想要就设计问题提出意见。如果你有一个C ++类而不是拥有其他对象,你会使用智能指针来实现这个吗?
class Example {
public:
// ...
private:
boost::scoped_ptr<Owned> data;
};
“拥有”对象不能按值存储,因为它可能会在对象的生命周期内发生变化。
我的观点是,一方面,你明确表示该对象是拥有的并确保删除它,但另一方面,你可以很容易地只有一个常规指针并在析构函数中删除它。这有点过头了吗?
跟进:只是想感谢您的所有答案。感谢关于auto_ptr的抬头在复制整个对象时使用NULL指针留下另一个对象,我已经广泛使用了auto_ptr但是还没想到。除非我有充分的理由,否则基本上我的所有类都是boost :: noncopyable,所以没有什么可担心的。还要感谢有关异常中内存泄漏的信息,这也是很好的。我尽量不编写可能导致构造函数中的异常的东西 - 有更好的方法可以做到这一点 - 所以这应该不是问题。
我刚才有另一个问题。当我问这个问题时,我想知道是否有人真的这样做了,你们似乎都提到理论上这是一个好主意,但没有人说他们真的这样做了。这让我感到惊讶!当然,一个拥有指向另一个对象的对象并不是一个新想法,我希望你们在某个时刻之前都会做到这一点。发生了什么事?
答案 0 :(得分:40)
scoped_ptr非常适合这个目的。但是必须要理解它的语义。您可以使用两个主要属性对智能指针进行分组:
这是相当常见的术语。对于智能指针,有一个特定的术语可以更好地标记这些属性:
让我们使用(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_ptr<int> a(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实现您的课程,请注意您要么满足以下两个要点之一:
Owned
成为一个完全定义的类。 否则,当你创建一个Example对象时,编译器会隐式为你定义一个析构函数,它会调用scoped_ptr的析构函数:
~Example() { ptr.~scoped_ptr<Owned>(); }
那会使scoped_ptr调用boost::checked_delete
,如果你没有完成上述两点中的任何一点,就会抱怨Owned
不完整。如果您在.cpp文件中定义了自己的dtor,则对.scpp文件的析构函数的隐式调用将来自.cpp文件,您可以在其中放置Owned
类的定义。
你对auto_ptr也有同样的问题,但是你还有一个问题:提供auto_ptr的类型不完整是当前未定义的行为(可能会为下一个C ++版本修复)。因此,当您使用auto_ptr时,您拥有以在头文件中使Owned成为完整类型。
shared_ptr没有这个问题,因为它使用了一个多态删除器,它会间接调用delete。因此,在析构函数实例化时不会实例化删除函数,但是在shared_ptr的构造函数中创建删除函数。
答案 1 :(得分:29)
这是一个好主意。它有助于简化代码,并确保在对象的生命周期内更改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期间,第二个“new Owned”会抛出异常(例如,内存不足)。 o1将被泄露,因为C ::〜C()(析构函数)不会被调用,因为该对象尚未完全构造。任何完全构造的成员字段的析构函数都会被调用。因此,使用scoped_ptr而不是普通指针将允许正确销毁o1。
答案 2 :(得分:8)
这根本不算太过分,这是一个好主意。
但它确实要求您的班级客户了解提升。这可能是也可能不是问题。为了便于携带,您可以考虑使用std :: auto_ptr(在这种情况下)执行相同的工作。由于它是私密的,您不必担心其他人试图复制它。
答案 3 :(得分:5)
使用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有多容易。
答案 4 :(得分:4)
Scoped指针对此非常有用,因为它们可以确保删除对象,而不必担心程序员。我认为这是一个很好用的范围ptr。
我发现一个好的设计策略一般是尽量避免手动释放内存,让你的工具(在这种情况下是智能指针)为你做。手动删除是一个主要原因,因为我可以看到它,这就是代码很难很快维护。分配和释放内存的逻辑通常在代码中是分开的,这导致互补的行不能保持在一起。
答案 5 :(得分:4)
我不认为这有点过分,这比使用原始指针更好地记录了成员的语义,并且不容易出错。
答案 6 :(得分:3)
为何过度杀伤? boost :: scoped_ptr非常容易优化,我敢打赌,生成的机器代码与手动删除析构函数中的指针相同。
scoped_ptr很好 - 只需使用它:)