Причина, по которой он работает для 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
также не «запоминает» исходный тип указателя, поскольку он должен быть максимально легким (один указатель).