tags:

views:

2766

answers:

6

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?

A: 

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.

Marco M.
_Strongly_ disagree. This approach does not defend against exceptions caused in the ctor itself, that may happen after your impl object is created. When this happens, the dtor is not called, and your impl object is leaked.
Chris Jester-Young
If you use the pimpl idiom it isn't unlikly that the impl pointer is the only pointer in the class. Using unmanaged pointers in that case is problem free if the destructor deletes the pointer. Resource leaks in the constructor is not possible because with only one member, only new can throw and will not allocate memory.
daramarak
+8  A: 

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.

fizzer
That reminds me, if your class does intend to be copyable (e.g., for use in STL containers), then either shared_ptr is required (if you want to share the impl), or else a deep copy of the impl.
Chris Jester-Young
Can you please provide more details on why it is undefined behavior if you not have a user-defined destructor (maybe standard section?)
Johannes Schaub - litb
It's not explicitly stated in the Standard, but is a consequence of the compiler having to instantiate an inline default dtor of the outer class when it sees that no dtor has been declared. As there is an auto_ptr<impl> member variable, the default dtor must call auto_ptr<impl> dtor.
fizzer
So ~auto_ptr<impl> must get instantiated here and will include a call to `delete impl_;` At this point in the translation unit, `impl_` is incomplete, so the delete is undefined by 5.3.5 (3). Real compilers (e.g MSVC++) will warn about deleting an incomplete type here.
fizzer
In contrast, if you declare a dtor, the compiler will not generate a default one. Later, in your cpp file you define struct impl at the top, and later on the (possibly empty) body of your dtor. Again, ~auto_ptr<impl> gets instantiated, but this time impl is complete and the delete defined.
fizzer
thanks for the explanation. this makes sense. upvoted :)
Johannes Schaub - litb
I think this is an unintended consequence of the Standard. If you search comp.lang.c++.moderated for 'auto_ptr incomplete type', you will find better analysis by people smarter than me. See also the boost smart_ptr pages which IIRC discuss the issue and why some of their pointers don't suffer it.
fizzer
I just remembered, in this case you're not choosing between standard library versus Boost, you're choosing between pre-TR1 and TR1 standard libraries. :-)
Chris Jester-Young
+4  A: 

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.

kshahar
+1  A: 

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.

Charles Bailey
shared_ptr and scoped_ptr _are_ in the standard libraries...of TR1. :-)
Chris Jester-Young
True, but TR1 is not part of either of the 1998 or 2003 standards and there are many C++ environments that implement these standards reasonably completely but don't provide TR1 libraries. The fewer dependencies you have; the more portable your code.
Charles Bailey
shared_ptr is in TR1. scoped_ptr is not.
Rimo
+23  A: 

You shouldn't really use std::auto_ptr for this. The destructor wont be visible at the point you declare the std::auto_ptr, so it might not be called properly. This is assuming that you are froward 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 wont be sharing the pimpl with any other objects, and this is enforced by scopped_ptr being noncopyable), you only need the pinpl 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 wont know how to call the Pimpl destructor.

boost::scoped_ptr (and shared_ptr) don't have this problem, because when call the constructor of a scoped_ptr (or the rest 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 no 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::scopped_ptr for this kind of thing.

Wilka
Brilliant explanation - thank you very much.
Rob
But you can't call pimpl(new Pimpl(blah)) when you use scoped_ptr since it's not copyable. So isn't shared_ptr better?
Is there a reason that you declare the Pimpl class outside MyClass? I ask because I've gotten into the habit of using a private struct for the pimpl object.
jamuraa
+2  A: 

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.

Even if it is tailored for the pimpl idiom it brings its nature with itself; you share the impl, which means that by default two copies of the object will in fact be the same object. That behavior is surprising, and the last think you want to do is to surprise the users of your class.
daramarak