Почему boost :: scoped_ptr не работает в сценарии наследования? - PullRequest
1 голос
/ 20 июля 2011

При использовании boost :: scoped_ptr для хранения ссылки деструктор производного объекта не вызывается.Это происходит при использовании 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;
}

Можете ли вы объяснить это?Есть ли "золотое правило" не использовать scoped_ptr для полиморфных переменных-членов?

Ответы [ 3 ]

18 голосов
/ 20 июля 2011

Причина, по которой он работает для shared_ptr, заключается в том, что он реализует специальный конструктор и метод reset(), который выглядит следующим образом:

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);
    // ....
};

Когда вызывается этот конструктор или reset(), shared_ptr «запоминает» исходный тип Y, поэтому, когда счетчик ссылок становится равным нулю, он будет правильно вызывать delete.(Конечно, p должен быть преобразован в T.) Это явно указано в документации :

[Этот конструктор был изменен на шаблон для того, чтобыпомните фактический тип переданного указателя.Деструктор будет вызывать delete с тем же указателем, в комплекте с его исходным типом, даже если T не имеет виртуального деструктора или имеет значение void....]

Конструктор scoped_ptr и reset() выглядит следующим образом:

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

Так что нетспособ scoped_ptr «запомнить», какой был исходный тип.И когда приходит время к delete указателю, это, по существу, делает это :

delete this->get();

И scoped_ptr<T>::get() возвращает T*.Поэтому, если scoped_ptr указывает на что-то, что не является T, а фактически подклассом T, вам необходимо реализовать деструктор virtual:

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

Так почему бы не scoped_ptrреализовать специальный конструктор для этой ситуации, как shared_ptr делает?Потому что «шаблон scoped_ptr - это простое решение для простых нужд» .shared_ptr ведет большую бухгалтерию, чтобы реализовать ее обширную функциональностьОбратите внимание, что intrusive_ptr также не «запоминает» исходный тип указателя, поскольку он должен быть максимально легким (один указатель).

8 голосов
/ 20 июля 2011

В отличие от shared_ptr<>, scoped_ptr<> не «запоминает» точный тип, который вы передаете его конструктору. http://www.boost.org/doc/libs/1_39_0/libs/smart_ptr/shared_ptr.htmsynopsis говорит:

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

};

т.е. он не может знать, что именно вы передаете, он знает только T, что в вашем случае составляет Base. Чтобы включить правильное удаление, вам нужно либо использовать shared_ptr<Base>, если это соответствует вашему дизайну, либо у вас должен быть Base виртуальный деструктор

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

Практическое правило (см. Также Мейерс):

Сделать деструкторы базового класса виртуальными, если вы хотите полиморфно удалить через базовый класс.

В отличие от scoped_ptr<>, shared_ptr<> явно запоминает тип указателя, который вы передаете конструктору:

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

и документ говорит

Этот конструктор был изменен на шаблон для запоминания фактического переданного типа указателя. Деструктор вызовет delete с тем же указателем, в комплекте с его исходным типом, даже если у T нет виртуального деструктора или он пуст.

Это стало возможным благодаря смешению среды выполнения со статическим полиморфизмом.

4 голосов
/ 20 июля 2011

Вам нужен виртуальный деструктор, чтобы деструктор производного класса вызывался через указатель на его базовый класс.

...