C ++使用scoped_ptr作为成员变量

时间:2009-02-01 11:45:59

标签: c++ oop smart-pointers

想要就设计问题提出意见。如果你有一个C ++类而不是拥有其他对象,你会使用智能指针来实现这个吗?

class Example {
public: 
  // ...

private:
  boost::scoped_ptr<Owned> data;
};

“拥有”对象不能按值存储,因为它可能会在对象的生命周期内发生变化。

我的观点是,一方面,你明确表示该对象是拥有的并确保删除它,但另一方面,你可以很容易地只有一个常规指针并在析构函数中删除它。这有点过头了吗?

跟进:只是想感谢您的所有答案。感谢关于auto_ptr的抬头在复制整个对象时使用NULL指针留下另一个对象,我已经广泛使用了auto_ptr但是还没想到。除非我有充分的理由,否则基本上我的所有类都是boost :: noncopyable,所以没有什么可担心的。还要感谢有关异常中内存泄漏的信息,这也是很好的。我尽量不编写可能导致构造函数中的异常的东西 - 有更好的方法可以做到这一点 - 所以这应该不是问题。

我刚才有另一个问题。当我问这个问题时,我想知道是否有人真的这样做了,你们似乎都提到理论上这是一个好主意,但没有人说他们真的这样做了。这让我感到惊讶!当然,一个拥有指向另一个对象的对象并不是一个新想法,我希望你们在某个时刻之前都会做到这一点。发生了什么事?

7 个答案:

答案 0 :(得分:40)

scoped_ptr非常适合这个目的。但是必须要理解它的语义。您可以使用两个主要属性对智能指针进行分组:

  • 可复制:可以复制智能指针:副本和原始共享所有权。
  • 可移动:可以移动智能指针:移动结果将拥有所有权,原始将不再拥有。

这是相当常见的术语。对于智能指针,有一个特定的术语可以更好地标记这些属性:

  • 所有权转让:智能指针是Movable
  • 所有权份额:智能指针是可复制的。如果一个智能指针已经可以复制,那么很容易支持所有权转移语义:那就是一个原子副本&amp; reset-of-original 操作,将其限制为某些类型的智能指针(例如,只有临时智能指针)。

让我们使用(C)opyable(M)ovable(N)either对可用的智能指针进行分组:

  1. boost::scoped_ptr:N
  2. std::auto_ptr:M
  3. boost::shared_ptr:C
  4. 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实现您的课程,请注意您要么满足以下两个要点之一:

    • 在班级的.cpp文件中写一个析构函数(即使它是空的),或者
    • 使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很好 - 只需使用它:)