只想对设计问题提出意见.如果你有一个C++类而不是拥有其他对象,你会使用智能指针来实现吗?
class Example { public: // ... private: boost::scoped_ptrdata; };
"拥有"对象不能按值存储,因为它可能会在对象的生命周期内发生变化.
我的观点是,一方面,你明确表示对象是拥有的并确保删除它,但另一方面,你可以很容易地只有一个常规指针并在析构函数中删除它.这有点矫枉过正吗?
跟进:只是想感谢您的所有答案.感谢关于auto_ptr的抬头在复制整个对象时使用NULL指针留下另一个对象,我已经广泛使用了auto_ptr但是还没想到.除非我有充分的理由,否则基本上我的所有类都是boost :: noncopyable,所以没有什么可担心的.还要感谢有关异常中内存泄漏的信息,这也是很好的.我尽量不编写可能导致构造函数中的异常的东西 - 有更好的方法可以做到这一点 - 所以这应该不是问题.
我只是有另一个问题.当我问这个问题时,我想知道是否有人真的这样做了,你们似乎都提到理论上这是一个好主意,但没有人说他们真的这样做了.这让我感到惊讶!当然,一个拥有指向另一个对象的对象并不是一个新想法,我希望你们在某个时刻之前都会做到这一点.这是怎么回事?
scoped_ptr非常适合这个目的.但是必须要理解它的语义.您可以使用两个主要属性对智能指针进行分组:
可复制:可以复制智能指针:副本和原始共享所有权.
移动:智能指针可以移动:这一举措,结果将拥有所有权,原来不会再拥有.
这是相当常见的术语.对于智能指针,有一个特定的术语可以更好地标记这些属性:
所有权转移:智能指针是Movable
所有权份额:智能指针是可复制的.如果一个智能指针已经是可复制,可以很容易地支持转移的所有权语义:那则只是一个原子的副本及复位的原装操作,限制,为某些类型(如只是暂时的智能指针)的智能指针.
让我们集团现有的智能指针,使用(C)opyable
,以及(M)ovable
,(N)either
:
boost::scoped_ptr
:N
std::auto_ptr
:M
boost::shared_ptr
: C
auto_ptr
有一个大问题,因为它使用复制构造函数实现Movable概念.这是因为当auto_ptr被C++接受时,还没有办法使用移动构造函数本地支持移动语义,而不是新的C++标准.也就是说,你可以使用auto_ptr执行以下操作,它可以工作:
auto_ptra(new int), b; // oops, after this, a is reset. But a copy was desired! // it does the copy&reset-of-original, but it's not restricted to only temporary // auto_ptrs (so, not to ones that are returned from functions, for example). b = a;
无论如何,正如我们所看到的,在您的情况下,您将无法将所有权转移到另一个对象:您的对象实际上是不可复制的.在下一个C++标准中,如果你继续使用scoped_ptr,它将是不可移动的.
要使用scoped_ptr实现您的类,请注意您要么满足以下两个点中的一个:
在类的.cpp文件中写一个析构函数(即使它是空的),或者
创建Owned
一个完全定义的类.
否则,当你创建一个Example对象时,编译器会隐式为你定义一个析构函数,它会调用scoped_ptr的析构函数:
~Example() { ptr.~scoped_ptr(); }
那将会使scoped_ptr调用boost::checked_delete
,Owned
如果你没有完成上述两点中的任何一点,就会抱怨它是不完整的.如果您在.cpp文件中定义了自己的dtor,则对.scpp文件的析构函数的隐式调用将来自.cpp文件,您可以在其中放置Owned
类的定义.
你有同样的问题与auto_ptr,但你还有一个问题:提供auto_ptr与不完整的类型是当前未定义的行为(可能会修复下一个C++版本).因此,当您使用auto_ptr时,您必须在头文件中使Owned成为完整类型.
shared_ptr没有这个问题,因为它使用多态删除器,它间接调用删除.因此,在析构函数实例化时不会实例化删除函数,但是在shared_ptr的构造函数中创建删除函数.
这是个好主意.它有助于简化代码,并确保在对象的生命周期内更改Owned对象时,前一个对象会被正确销毁.
你必须记住scoped_ptr是不可复制的,这使得你的类默认情况下是不可复制的,直到/除非你添加你自己的复制构造函数等.(当然,在原始指针的情况下使用默认的复制构造函数将是 - 不也!)
如果您的类有多个指针字段,那么在一种情况下使用scoped_ptr实际上可以提高异常安全性:
class C { Owned * o1; Owned * o2; public: C() : o1(new Owned), o2(new Owned) {} ~C() { delete o1; delete o2;} };
现在,假设在构造C期间,第二个"新拥有者"抛出异常(例如,内存不足).o1将被泄露,因为C ::〜C()(析构函数)不会被调用,因为该对象尚未完全构造.任何未完全建立成员字段的析构函数不会得到所谓的虽然.因此,使用scoped_ptr而不是普通指针将允许正确销毁o1.
它根本不是矫枉过正,这是个好主意.
但它确实要求您的班级客户了解提升.这可能是也可能不是问题.为了便于携带,您可以考虑使用std :: auto_ptr(在这种情况下)执行相同的工作.由于它是私有的,您不必担心其他人试图复制它.
使用scoped_ptr是一个好主意。
保持和手动销毁指针并不像您想的那么简单。特别是如果您的代码中有多个RAW指针。如果异常安全性和不泄漏内存是优先事项,那么您需要大量额外的代码来使其正确。
首先,必须确保正确定义所有四个默认方法。这是因为这些方法的编译器生成版本适用于普通对象(包括智能指针),但在正常情况下会导致指针处理问题(查找浅拷贝问题)。
默认构造函数
复制构造函数
赋值运算符
析构函数
如果使用scoped_ptr,则无需担心其中任何一个。
现在,如果您的类中有多个RAW指针(或构造函数的其他部分可以抛出)。您必须在构造和销毁过程中明确处理异常。
class MyClass { public: MyClass(); MyClass(MyClass const& copy); MyClass& operator=(MyClass const& copy); ~MyClass(); private Data* d1; Data* d2; }; MyClass::MyClass() :d1(NULL),d2(NULL) { // This is the most trivial case I can think off // But even it looks ugly. Remember the destructor is NOT called // unless the constructor completes (without exceptions) but if an // exception is thrown then all fully constructed object will be // destroyed via there destructor. But pointers don't have destructors. try { d1 = new Data; d2 = new Data; } catch(...) { delete d1; delete d2; throw; } }
看看scopted_ptr有多容易。