std::auto_ptr or boost::shared_ptr for pImpl idiom

2019-01-31 02:32发布

问题:

When using the pImpl idiom is it preferable to use a boost:shared_ptr instead of a std::auto_ptr? I'm sure I once read that the boost version is more exception friendly?

class Foo
{
public:
    Foo();
private:
    struct impl;
    std::auto_ptr<impl> impl_;
};

class Foo
{
public:
    Foo();
private:
    struct impl;
    boost::shared_ptr<impl> impl_;
};

[EDIT] Is it always safe to use std::auto_ptr<> or are there situations when an alternative boost smart pointer is required?

回答1:

You shouldn't really use std::auto_ptr for this. The destructor won't be visible at the point you declare the std::auto_ptr, so it might not be called properly. This is assuming that you are forward declaring your pImpl class, and creating the instance inside the constructor in another file.

If you use boost::scoped_ptr (no need for shared_ptr here, you won't be sharing the pimpl with any other objects, and this is enforced by scoped_ptr being noncopyable), you only need the pimpl destructor visible at the point you call the scoped_ptr constructor.

E.g.

// in MyClass.h

class Pimpl;

class MyClass 
{ 
private:
    std::auto_ptr<Pimpl> pimpl;

public: 
    MyClass();
};

// Body of these functions in MyClass.cpp

Here, the compiler will generate the destructor of MyClass. Which must call auto_ptr's destructor. At the point where the auto_ptr destructor is instantiated, Pimpl is an incomplete type. So in to the auto_ptr destructor when it deletes the the Pimpl object, it won't know how to call the Pimpl destructor.

boost::scoped_ptr (and shared_ptr) don't have this problem, because when you call the constructor of a scoped_ptr (or the reset method) it also makes a function-pointer-equivalent that it will use instead of calling delete. The key point here is that it instantiates the deallocation function when Pimpl is not an incomplete type. As a side note, shared_ptr allows you to specify a custom deallocation function, so you can use for it for things like GDI handles or whatever else you may want - but that's overkill for your needs here.

If you really want to use std::auto_ptr, then you need to take extra care by making sure you define your MyClass destructor in MyClass.cpp when Pimpl is fully defined.

// in MyClass.h

class Pimpl;

class MyClass 
{ 
private:
    std::auto_ptr<Pimpl> pimpl;

public: 
    MyClass();
    ~MyClass();
};

and

// in MyClass.cpp

#include "Pimpl.h"

MyClass::MyClass() : pimpl(new Pimpl(blah))
{
}

MyClass::~MyClass() 
{
    // this needs to be here, even when empty
}

The compiler will generate the code destruct all of the MyClass members effectively 'in' the empty destructor. So at the point the auto_ptr destructor is instantiated, Pimpl is no longer incomplete and the compiler now knows how to call the destructor.

Personally, I don't think it's worth the hassle of making sure everything is correct. There's also the risk that somebody will come along later and tidy up the code by removing the seemingly redundant destructor. So it's just safer all round to go with boost::scoped_ptr for this kind of thing.



回答2:

I tend to use auto_ptr. Be sure to make your class noncopyable (declare private copy ctor & operator=, or else inherit boost::noncopyable). If you use auto_ptr, one wrinkle is that you need to define a non-inline destructor, even if the body is empty. (This is because if you let the compiler generate the default destructor, impl will be an incomplete type when the call to delete impl_ is generated, invoking undefined behaviour).

There's little to choose between auto_ptr & the boost pointers. I tend not to use boost on stylistic grounds if a standard library alternative will do.



回答3:

The boost alternative to std::auto_ptr is boost::scoped_ptr. The main difference from auto_ptr is that boost::scoped_ptr is noncopyable.

See this page for more details.



回答4:

boost::shared_ptr is specially tailored to work for pimpl idiom. One of the main advantages is that it allows not to define the destructor for the class holding pimpl. Shared ownership policy maybe both advantage and disadvantage. But in later case you can define copy constructor properly.



回答5:

If you are being really pedantic there is no absolute guarantee that using an auto_ptr member does not require a full definition of the auto_ptr's template parameter at the point at which it is used. Having said that, I've never seen this not work.

One variation is to use a const auto_ptr. This works so long as you can construct your 'pimpl' with a new expression inside the initialiser list and guarantees that the compiler cannot generate default copy constructor and assignment methods. A non-inline destructor for the enclosing class still needs to be provided.

Other things being equal, I would favour an implementation that uses just the standard libraries as it keeps things more portable.



回答6:

If you want a copyable class, use scoped_ptr, which forbids copying, thus making your class hard to use wrong by default (compared to using shared_ptr, the compiler won't emit copy facilities on its own; and in case of shared_ptr, if you don't know what you do [which is often enough the case even for wizards], there would be strange behaviour when suddenly a copy of something also modifies that something), and then out-define a copy-constructor and copy-assignment:

class CopyableFoo {
public:
    ...
    CopyableFoo (const CopyableFoo&);
    CopyableFoo& operator= (const CopyableFoo&);
private:
    scoped_ptr<Impl> impl_;
};

...
CopyableFoo (const CopyableFoo& rhs)
    : impl_(new Impl (*rhs.impl_))
{}


回答7:

shared_ptr is much preferable to auto_ptr for pImpl because your outer class could suddenly end up losing its pointer when you copy it.

With shared_ptr you can use a forwardly-declared type so that works. auto_ptr does not allow a forwardly-declared type. Nor does scoped_ptr and if your outer class is going to be non-copyable anyway and only has one pointer, it may as well be a regular one.

There is a lot to be said for using an intrusive reference count in the pImpl and get the outer class to call its copy and assign semantics in its implementation. Assuming this is a true vendor (supplies the class) model it is better the vendor does not force the user to be using shared_ptr, or to be using the same version of shared_ptr (boost or std).



回答8:

I have been really happy about impl_ptr by Vladimir Batov [modified]. It makes it really easy to create a pImpl without needing to make explicit copy-constructor and assignment operator.

I have modified the original code, so it now resembles a shared_ptr, so it can be used in epilog code, and remains speedy.



回答9:

Don't try so hard to shoot yourself in the foot, in C++ you have plenty of opportunities :) There is no real need to use either auto pointers, since you perfectly know when your object should go in and out of life (in your constructor(s) and destructor).

Keep it simple.