简体   繁体   English

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

[英]C++ using scoped_ptr as a member variable

Just wanted opinions on a design question. 只是想要关于设计问题的意见。 If you have a C++ class than owns other objects, would you use smart pointers to achieve this? 如果您拥有一个C ++类而不是拥有其他对象,那么您会使用智能指针来实现这一点吗?

class Example {
public: 
  // ...

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

The 'Owned' object can't be stored by value because it may change through the lifetime of the object. “拥有的”对象无法按值存储,因为它可能会在对象的整个生命周期内发生变化。

My view of it is that on the one side, you make it clear that the object is owned and ensure its deletion, but on the flipside, you could easily just have a regular pointer and delete it in the destructor. 我的观点是,一方面,您可以清楚地知道该对象已拥有并确保将其删除,但另一方面,您可以轻松地拥有一个常规指针并将其删除到析构函数中。 Is this overkill? 这是过度杀伤力吗?

Follow up: Just wanted to say thanks for all your answers. 后续行动:只想对您的所有回答表示感谢。 Thanks for the heads-up about auto_ptr leaving the other object with a NULL pointer when the whole object is copied, I have used auto_ptr extensively but had not thought of that yet. 感谢您对有关auto_ptr的注意,在复制整个对象时将另一个对象留有NULL指针,我已经广泛使用了auto_ptr,但还没有想到。 I make basically all my classes boost::noncopyable unless I have a good reason, so there's nothing to worry about there. 除非我有充分的理由,否则我基本上将所有类都设为boost :: noncopyable,因此不必担心。 And thanks also for the information on memory leaks in exceptions, that's good to know too. 同时也感谢您提供有关异常中内存泄漏的信息,这也很高兴。 I try not to write things which could cause exceptions in the constructor anyway - there are better ways of doing that - so that shouldn't be a problem. 我尽量不要写任何可能在构造函数中导致异常的东西-这样做有更好的方法-这样就不成问题。

I just had another question though. 我只是有另一个问题。 What I wanted when I asked this question was to know whether anyone actually did this, and you all seem to mention that it's a good idea theoretically, but no one's said they actually do it. 当我问这个问题时,我想知道是否有人真正做到了这一点,而且大家似乎都提到从理论上讲这是一个好主意,但是没有人说他们实际上做了。 Which surprises me! 这让我感到惊讶! Certainly one object owning a pointer to another is not a new idea, I would have expected you all would have done it before at some point. 当然,拥有指向另一个物体的指针的对象并不是一个新主意,我希望大家在某个时候都可以做到这一点。 What's going on? 这是怎么回事?

scoped_ptr is very good for this purpose. scoped_ptr对此非常有用。 But one has to understand its semantics. 但是必须理解它的语义。 You can group smart pointers using two major properties: 您可以使用两个主要属性对智能指针进行分组:

  • Copyable: A smart pointer can be copied: The copy and the original share ownership. 可复制:可以复制智能指针:副本和原始共享所有权。
  • Movable: A smart pointer can be moved: The move-result will have ownership, the original won't own anymore. 可移动:可移动智能指针:移动结果将拥有所有权,原始将不再拥有。

That's rather common terminology. 那是相当普遍的术语。 For smart pointers, there is a specific terminology which better marks those properties: 对于智能指针,有一个特定的术语可以更好地标记这些属性:

  • Transfer of Ownership: A smart pointer is Movable 所有权转让:智能指针是可移动的
  • Share of Ownership: A smart pointer is copyable. 所有权共享:智能指针是可复制的。 If a smart pointer is already copyable, it's easy to support transfer-of-ownership semantic: That then is just an atomic copy & reset-of-original operation, restricting that to smart pointers of certain kinds (eg only temporary smart pointers). 如果智能指针已经是可复制的,则很容易支持所有权转移语义:这仅是原子复制和原始复位操作,将其限制为某些类型的智能指针(例如,仅临时智能指针)。

Let's group the available smart pointers, using (C)opyable , and (M)ovable , (N)either : 让我们使用(C)opyable(M)ovable(N)either对可用的智能指针进行(C)opyable

  1. boost::scoped_ptr : N boost::scoped_ptr :N
  2. std::auto_ptr : M std::auto_ptr :M
  3. boost::shared_ptr : C boost::shared_ptr :C

auto_ptr has one big problem, in that it realizes the Movable concept using a copy constructor. auto_ptr有一个大问题,因为它使用复制构造函数实现了Movable概念。 That is because When auto_ptr was accepted into C++, there wasn't yet a way to natively support move semantics using a move constructor, as opposed to the new C++ Standard. 这是因为当auto_ptr被C ++接受时,与新的C ++标准相对,还没有一种方法可以使用move构造函数来本地支持move语义。 That is, you can do the following with auto_ptr, and it works: 也就是说,您可以使用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; 

Anyway, as we see, in your case you won't be able to transfer the ownership to another object: Your object will in effect be non-copyable. 无论如何,正如我们所见,在您的情况下,您将无法将所有权转让给另一个对象:您的对象实际上将是不可复制的。 And in the next C++ Standard, it will be non-movable if you stay with scoped_ptr. 并且在下一个C ++标准中,如果您继续使用scoped_ptr,它将是不可移动的。

For implementing your class with scoped_ptr, watch that you either have one of these two points satisfied: 为了使用scoped_ptr实现您的类,请注意满足以下两个条件之一:

  • Write an destructor (even if it's empty) in the .cpp file of your class, or 在类的.cpp文件中编写一个析构函数(即使它为空),或者
  • Make Owned a completely defines class. 使自己Owned一个完全定义的类。

Otherwise, when you would create an object of Example, the compiler would implicitly define a destructor for you, which would call scoped_ptr's destructor: 否则,当您创建Example对象时,编译器将为您隐式定义一个析构函数,该析构函数将调用scoped_ptr的析构函数:

~Example() { ptr.~scoped_ptr<Owned>(); }

That would then make scoped_ptr call boost::checked_delete , which would complain about Owned being incomplete, in case you haven't done any of the above two points. 然后,这将使scoped_ptr调用boost::checked_delete ,如果您没有完成以上两点中的任何一项,则会抱怨Owned不完整。 If you have defined your own dtor in the .cpp file, the implicit call to the destructor of scoped_ptr would be made from the .cpp file, in which you could place the definition of your Owned class. 如果在.cpp文件中定义了自己的dtor,则将从.cpp文件进行对scoped_ptr的析构函数的隐式调用,您可以在其中放置Owned类的定义。

You have that same problem with auto_ptr, but you have one more problem: Providing auto_ptr with an incomplete type is undefined behavior currently (maybe it will be fixed for the next C++ version). 您对auto_ptr遇到同样的问题,但还有另一个问题:为auto_ptr提供不完整的类型当前是未定义的行为(也许将在下一个C ++版本中得到修复)。 So, when you use auto_ptr, you have to make Owned a complete type within your header file. 因此,当您使用auto_ptr时, 必须在标头文件中将“拥有的个人”设置为完整类型。

shared_ptr doesn't have that problem, because it uses a polymorphic deleter, which makes an indirect call to the delete. shared_ptr没问题,因为它使用了多态删除器,该删除器对删除进行了间接调用。 So the deleting function is not instantiated at the time the destructor is instantiated, but at the time the deleter is created in shared_ptr's constructor. 因此,删除函数不会在实例化析构函数时实例化,而是在shared_ptr的构造函数中创建删除程序时才会实例化。

It's a good idea. 这是一个好主意。 It helps simplify your code, and ensure that when you do change the Owned object during the lifetime of the object, the previous one gets destroyed properly. 它有助于简化代码,并确保当您在对象的生存期内更改“拥有”对象时,前一个对象被正确销毁了。

You have to remember that scoped_ptr is noncopyable, though, which makes your class noncopyable by default until/unless you add your own copy constructor, etc. (Of course, using the default copy constructor in the case of raw pointers would be a no-no too!) 您必须记住,scoped_ptr是不可复制的,这使您的类在默认情况下不可复制,直到/除非您添加自己的副本构造函数,等等。(当然,在使用原始指针的情况下,使用默认副本构造函数将是非复制操作,也不行!)

If your class has more than one pointer field, then use of scoped_ptr actually improves exception safety in one case: 如果您的类具有多个指针字段,那么在以下情况下,使用scoped_ptr实际上可以提高异常安全性:

class C
{
  Owned * o1;
  Owned * o2;

public:
  C() : o1(new Owned), o2(new Owned) {}
  ~C() { delete o1; delete o2;}
};

Now, imagine that during construction of a C the second "new Owned" throws an exception (out-of-memory, for example). 现在,想象一下在构造C的过程中,第二个“新拥有的”会抛出异常(例如,内存不足)。 o1 will be leaked, because C::~C() (the destructor) won't get called, because the object has not been completely constructed yet. o1将被泄漏,因为不会调用C ::〜C()(析构函数),因为该对象尚未完全构造。 The destructor of any completely constructed member field does get called though. 确实会调用任何完全构造的成员字段的析构函数。 So, using a scoped_ptr instead of a plain pointer will allow o1 to be properly destroyed. 因此,使用scoped_ptr而不是普通指针将允许o1被正确销毁。

It's not overkill at all, it's a good idea. 一点也不算过分,这是个好主意。

It does require your class clients to know about boost, though. 但是,这确实需要您的班级客户了解升压。 This may or may not be an issue. 这可能是问题,也可能不是问题。 For portability you could consider std::auto_ptr which does (in this case) the same job. 对于可移植性,您可以考虑使用std :: auto_ptr来完成(在这种情况下)相同的工作。 As it's private, you don't have to worry about other people attempting to copy it. 由于它是私人的,因此您不必担心其他人试图复制它。

Using the scoped_ptr is a good idea. 使用scoped_ptr是一个好主意。

Keeping and manually destroying the pointer is not as simple as you think. 保持和手动销毁指针并不像您想的那么简单。 Especially if there is more than one RAW pointer in your code. 特别是如果您的代码中有多个RAW指针。 If exception safety and not leaking memory is a priority then you need a lot of extra code to get it correct. 如果异常安全性和不泄漏内存是优先事项,那么您需要大量额外的代码来使其正确。

For a start you have to make sure you correctly define all four default methods. 首先,必须确保正确定义所有四个默认方法。 This is because the compiler generated version of these methods are fine for normal objects (including smart pointers) but in the normal case will lead to problems with pointer handling (Look for the Shallow Copy Problem). 这是因为这些方法的编译器生成版本适用于普通对象(包括智能指针),但在正常情况下会导致指针处理问题(查找“浅复制问题”)。

  • Default Constructor 默认构造函数
  • Copy Constructor 复制构造函数
  • Assignment Operator 赋值运算符
  • Destructor 析构函数

If you use the scoped_ptr then you don't need to worry about any of those. 如果使用scoped_ptr,则无需担心其中任何一个。

Now if you have more than one RAW pointer in your class (or other parts of your constructor can throw) . 现在,如果您的类中有多个RAW指针(或构造函数的其他部分可以抛出)。 You have to EXPLICITLY deal with exceptions during construction and destruction. 您必须在构造和销毁过程中明确处理异常。

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;
    }
}

Look how much easier a scopted_ptr is. 看看scopted_ptr有多容易。

Scoped pointers are good for exactly this because they ensure that the objects get deleted without you having to worry about it as a programmer. 有作用域的指针正是因为它可以确保删除对象而无需担心程序员的情况,因此可以很好地做到这一点。 I think that this is a good use of scoped ptr. 我认为这是作用域ptr的很好使用。

I find that a good design strategy in general is to avoid freeing memory manually as much as possible and let your tools (in this case smart pointers) do it for you. 我发现,一个好的设计策略通常是避免尽可能多地手动释放内存,并让您的工具(在这种情况下为智能指针)为您完成任务。 Manual deletion is bad for one main reason as I can see it, and that is that code becomes difficult to maintain very quickly. 正如我所看到的,手动删除是不好的,主要原因之一是,代码变得很难快速维护。 The logic for allocation and deallocation of memory is often separate in the code, and that leads to the complementary lines not being maintained together. 内存的分配和释放逻辑通常在代码中是分开的,这导致互补行无法保持在一起。

我认为这并不过分,它比具有原始指针更好地记录了成员的语义,并且不容易出错。

Why an overkill? 为什么要大刀阔斧? boost::scoped_ptr is very easy to optimize, and I bet the resulting machine code would be the same as if you manually delete the pointer in the destructor. boost :: scoped_ptr非常易于优化,我敢打赌,生成的机器代码将与您在析构函数中手动删除指针相同。

scoped_ptr is good - just use it :) scoped_ptr很好-只需使用它即可:)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM