我正在实现自己的双向共享所有权智能指针,因为我需要一种方法来替换托管对象--一组智能指针指向一个新对象(这是下面代码中的reset_all方法)。
我试图尽可能接近std::shared_ptr的S界面,同时避免了我不理解/不需要的东西,比如从其他智能指针类型中进行混叠和转换。
//(it's all in an header file)
#pragma once
template <typename T>
class Manager {
template <typename U>
friend class SharedPtr;
T* managed;
unsigned int counter;
Manager(T* new_ptr):
managed(new_ptr), counter(1) {}
~Manager() = default;
Manager<T>* own() {
++counter;
return this;
}
void disown() {
if (!(--counter)) {
delete managed;
delete this;
}
}
};
template <typename T>
class SharedPtr {
public:
SharedPtr() = default;
SharedPtr(T* new_ptr) {
if (new_ptr) {
manager = new Manager<T>(new_ptr);
}
}
SharedPtr(const SharedPtr &rhs):
manager(rhs.manager->own()) {}
SharedPtr(SharedPtr &&rhs):
manager(rhs.manager) {
rhs.manager = nullptr;
}
SharedPtr& operator=(const SharedPtr &rhs) {
if (manager) {
manager->disown();
}
manager = rhs.manager->own();
return *this;
}
SharedPtr& operator=(SharedPtr &&rhs) {
if (manager) {
manager->disown();
}
manager = rhs.manager;
rhs.manager = nullptr;
return *this;
}
void swap(SharedPtr &rhs) {
Manager<T>* tmp = manager;
manager = rhs.manager;
rhs.manager = tmp;
}
void reset() {
if (manager) {
manager->disown();
}
manager = nullptr;
}
void reset(T* new_ptr) {
if (manager) {
manager->disown();
}
manager = new Manager<T>(new_ptr);
}
T* get() {
if (manager) {
return manager->managed;
}
return nullptr;
}
T& operator*() {
return *(manager->managed);
}
T* operator->() {
return manager->managed;
}
unsigned int use_count() {
if (manager) {
return manager->counter;
}
return 0;
}
bool unique() {
return use_count() == 1;
}
operator bool() {
return (bool) manager;
}
void reset_all(T* new_ptr) {
if (manager) {
delete manager->managed;
manager->managed = new_ptr;
return;
}
manager = new Manager<T>(new_ptr);
}
~SharedPtr() {
if (manager) {
manager->disown();
}
}
private:
Manager<T>* manager = nullptr;
};发布于 2016-11-20 18:52:20
您已经确定托管对象可能是null (当所管理的对象是null时)。
就我个人而言,我认为总是有一个托管对象。这将使您的其余代码编写起来更简单(我喜欢更简单的代码),因为永远不会有空对象。
当然,这个论点的另一个方面是,允许一个null托管对象可以更好地管理资源。我还没有做过计算--所以我还没有得出结论,但这可能是值得做的,并在代码中进行注释。
您应该将所有代码放在自己的命名空间中。这也有助于使您的包含警卫独特。
并非所有编译器都支持此实用程序。
#pragma once所以更喜欢使用一种没有变化的系统。
class Manager不是在class SharedPtr之外使用的,所以我个人认为它是这个SharedPtr的私有成员类。
尽管您不进行任何复制,但显然Manager是不可复制的。因此,让它明确地不可复制,以确保您不会意外地这样做,这将是一个好主意。
做delete this;是非常危险的。
delete this;我宁愿更改接口,以便disown()的调用者对manager对象执行完全删除。
bool disown() {
return ((--counter) == 0);
}
~Manager() {
delete managed;
}当您有一个参数构造函数时,您必须非常小心。编译器将使用单个参数构造函数将一种类型急切地转换为另一种类型(在您最不期望的时候,这可能是一个问题)。
审查这一点:
actionOne(SharedPtr<int> const& val){/*Do Stuff*/}
int main()
{
int b;
actionOne(&b); // Caboom.
}在这里,编译器看到它将int*作为参数。但是它所拥有的函数的唯一版本需要SharedPtr<int> const,但这很容易通过直接构建SharedPtr<int>来实现,而且它将这样做。在这种情况下,这是非常危险的,因为指针没有被动态分配,而是将被删除。
因此,将单个参数构造函数显式化(特别是当它们获得所有权时)。
explicit SharedPtr(T* new_ptr);用
nullptr随着nullptr的引入,我们有一个可以用于null值的对象。但是,我没有看到用nullptr覆盖显式构造的构造函数。
explicit SharedPtr(nullptr_t);将移动构造函数标记为noexcept并将赋值操作符移动是一个好主意。这是因为(因为它们通常不是在创建资源),但这也允许使用标准库容器进行一些良好的优化。
SharedPtr(SharedPtr &&rhs) noexcept;
SharedPtr& operator=(SharedPtr &&rhs) noexcept;
void swap(SharedPtr &rhs) noexcept;为什么重写std::
void swap(SharedPtr &rhs) {
Manager<T>* tmp = manager;
manager = rhs.manager;
rhs.manager = tmp;
}
void swap(SharedPtr &rhs) noexcept {std::swap(manager, rhs.manager);} void reset(T* new_ptr) {
if (manager) {
manager->disown();
}
manager = new Manager<T>(new_ptr);
}在大多数情况下,nullptr会导致空管理器对象。但此重置将为空指针创建一个管理器对象。我会努力做到始终如一。
如果函数不改变对象的状态。那么它应该被标记为const。
unsigned int use_count() const;
bool unique() const;当前对bool的强制转换有点危险,因为编译器在尝试获取类型以匹配某些操作时将使用它。
考虑:
SharedPtr<int> val1(new int(4));
SharedPtr<int> val2(new int(8));
if (val1 == val2) {
std::cout << "Val1 and Val2 match\n"; // Will print out.
}这是因为编译器看到它可以通过将两个值转换为bool然后进行比较来进行比较。
您可以通过标记函数explicit来修复这个问题。
explicit operator bool() const;这只允许在布尔上下文中显式使用对象或显式转换为bool时将其转换为bool。
if (val1) // This works as expected as the expression for an if
// statement requires a boolean and thus it is considered
// a boolean context and will allow the bool cast operator
// to fire even if it is explicit.
{
std::cout << "Val1 is null\n";
}您正在混合业务和资源逻辑。
void reset_all(T* new_ptr) {
if (manager) {
delete manager->managed;
manager->managed = new_ptr;
return;
}
manager = new Manager<T>(new_ptr);
}如果用户希望将被管理的值设置为另一个值,则只需使用托管对象赋值操作符即可。
SharedPtr<int> x(new int(6));
x.reset_all(new int(5));
// or
*x = 5;我在这里详细描述了编写智能指针的所有问题:
独特ptr
共享ptr
构造函数
发布于 2016-11-19 16:56:04
SharedPtr,因为与std::shared_ptr的对比太大,而名称太similar.可能是DoublePtr或类似的,以强调双重间接,这是关键的功能.Manager成为类的私有非模板成员,因为它是一个实现细节。另外,我会把它变成一个愚蠢的结构,并将它包含的有用函数(disown)放到DoublePtr的私有部分中。nullptr.开始,因此我将删除ctor中的检查。get() != nullptr。reset和reset_all很容易成为从基本异常安全升级到强异常安全(提交或回滚),只需创建一个新的DoublePtr和交换即可。noexcept。const。发布于 2016-11-19 18:00:31
在操作符bool()中,我更喜欢显式的非空检查,如:
operator bool() {
return manager != nullptr;
}而不是抛出指针。其余的在我看来都很好。
https://codereview.stackexchange.com/questions/147517
复制相似问题