开发者

Why does boost::scoped_ptr not work in inheritance scenario?

When using boost::scoped_ptr to hold a reference, the destructor of the derived object is not called. It does when using boost::shared_ptr.

#include "stdafx.h"
#include <iostream>
#include "boost/scoped_ptr.hpp"
#include "boost/shared_ptr.hpp"

using namespace std;

class Base
{
public:
    Base() { cout << "Base constructor" << endl ; }
    ~Base() { cout << "Base destructor" << endl; }
};

class Derived : public Base
{
public:
    Derived() : Base() { cout << "Derived constructor" << endl; }
    ~Derived() { cout << "Derived destructor" << endl; }
};

int _tmain(int argc, _TCHAR* argv[])
{
    boost::scoped_ptr<Base> pb;  // replacing by shared_ptr does call Derived destructor
    pb.reset(new Derived());
    cout << "Program ends here" << endl;
}

Can you explain this? Is there a "golden rule" not to use scoped_ptr for polymorphic m开发者_如何学编程ember variables?


The reason why it works for shared_ptr is because it implements a special constructor and a reset() method that look like this:

template<class T>
class shared_ptr
{
public:
    // ...
    // Note use of template
    template<class Y> explicit shared_ptr(Y * p);
    // ....
    // Note use of template
    template<class Y> void reset(Y * p);
    // ....
};

When this constructor or reset() is called, shared_ptr "remembers" the original type Y so that when the reference count goes to zero, it will call delete properly. (Of course p must be convertible to T.) This is explicitly stated in the documentation:

[This constructor has been changed to a template in order to remember the actual pointer type passed. The destructor will call delete with the same pointer, complete with its original type, even when T does not have a virtual destructor, or is void. ...]

The scoped_ptr constructor and reset() look like this:

template<class T>
class scoped_ptr : noncopyable
{
public:
    // ...
    explicit scoped_ptr(T * p = 0);
    // ...
    void reset(T * p = 0);
};

So there's no way for scoped_ptr to "remember" what the original type was. And when it comes time to delete the pointer, it essentially does this:

delete this->get();

And scoped_ptr<T>::get() returns a T*. So if scoped_ptr points to something that's not a T but actually a subclass of T, you need to implement a virtual destructor:

class Base
{
public:
    Base() { cout << "Base constructor" << endl ; }
    virtual ~Base() { cout << "Base destructor" << endl; }
};

So why doesn't scoped_ptr implement a special constructor for this situation like shared_ptr does? Because "scoped_ptr template is a simple solution for simple needs". shared_ptr does a lot of bookkeeping to implement its extensive functionality. Note that intrusive_ptr also doesn't "remember" the original type of the pointer since it's meant to be as lightweight as possible (one pointer).


Unlike shared_ptr<>, scoped_ptr<> does not "remember" the exact type you pass to its constructor. The http://www.boost.org/doc/libs/1_39_0/libs/smart_ptr/shared_ptr.htmsynopsis tells:

template<class T> class scoped_ptr : noncopyable {

public:
 typedef T element_type;

 explicit scoped_ptr(T * p = 0); // never throws
 ~scoped_ptr(); // never throws

 void reset(T * p = 0); // never throws

 T & operator*() const; // never throws
 T * operator->() const; // never throws
 T * get() const; // never throws

 operator unspecified-bool-type() const; // never throws

 void swap(scoped_ptr & b); // never throws

};

I.e. it can't know what exactly you pass, it only knows T, which is Base in your case. In order to enable correct deletion, you either need to use shared_ptr<Base> if that would fit your design, or you must have your Base have a virtual destructor

class Base
{
public:
    Base() { cout << "Base constructor" << endl ; }
    virtual ~Base() { cout << "Base destructor" << endl; }
};

As a rule of thumb (see also Meyers):

Make base-class destructors virtual if you want to delete through the base-class polymorphically.

Unlike scoped_ptr<>, shared_ptr<> explicitly remembers the pointer type you pass to the constructor:

...
template<class Y> shared_ptr(shared_ptr<Y> const & r);
...

and the doc says

This constructor has been changed to a template in order to remember the actual pointer type passed. The destructor will call delete with the same pointer, complete with its original type, even when T does not have a virtual destructor, or is void.

This is made possible by mixing a runtime- with static-polymorphism.


You need to have a virtual destructor in order for a derived class destructor to be called through a pointer to its base class.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜