Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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?

like image 777
Rob Avatar asked Nov 22 '08 10:11

Rob


1 Answers

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.

like image 113
Wilka Avatar answered Oct 14 '22 12:10

Wilka