典型的工厂设计模式要求基类声明虚拟析构函数,但使用shared_ptr
实际上可以避免这种情况。
#include <iostream>
#include <memory>
#include <cstdio>
using namespace std;
class Dog {
public:
~Dog() { cout << "dog destroyedn"; }
};
class Yellowdog : public Dog {
public:
~Yellowdog() { cout << "Yellow dog destroyed.n"; }
};
class DogFactory {
public:
static shared_ptr<Dog> createYellowdog() {
return shared_ptr<Yellowdog>(new Yellowdog());
}
};
int main(int argc, char *argv[]) {
auto ptr = DogFactory::createYellowdog();
cout << ptr.use_count() << endl;
return 0;
}
在这种情况下,输出是yellowdog destroyed
,然后是dog destroyed
。但为什么呢?为什么使用shared_ptr
可以省略~Dog
之前的虚拟关键字?
之所以会发生这种情况,是因为shared_ptr
在创建第一个shared_ptr
时创建的控制块中存储类型已擦除的deleter。在您的例子中,shared_ptr
是为黄狗创建的,deleter将调用黄狗析构函数。
当您将一个shared_ptr
复制(或复制构造)到另一个时,它们共享相同的控制块,并且新的共享ptr将从原始ptr调用deleter,也就是将调用yellowdog析构函数的ptr。
然而,它并没有真正使类具有多态性,也不适合工厂实现——类中的任何其他非虚拟函数都将基于静态类型shared_ptr
进行调用,您不希望在多态类中出现这种情况。
SergeyA所说的完全正确,对于那些想要查看真实代码的人来说,以下是LLVM的shared_ptr实现(我只显示了与解释相关的部分代码):
template<class _Tp>
class _LIBCPP_SHARED_PTR_TRIVIAL_ABI _LIBCPP_TEMPLATE_VIS shared_ptr
{
//Constructor taking a pointer
template<class _Yp, class = _EnableIf<
_And<__compatible_with<_Yp, _Tp>>::value>
>
explicit shared_ptr(_Yp* __p) : __ptr_(__p) {
unique_ptr<_Yp> __hold(__p);
typedef typename __shared_ptr_default_allocator<_Yp>::type _AllocT;
typedef __shared_ptr_pointer<_Yp*, __shared_ptr_default_delete<_Tp, _Yp>, _AllocT > _CntrlBlk;
__cntrl_ = new _CntrlBlk(__p, __shared_ptr_default_delete<_Tp, _Yp>(), _AllocT());
__hold.release();
__enable_weak_this(__p, __p);
}
...
//other code omited
}
//Move Constructor
template<class _Tp>
template<class _Yp>
inline
shared_ptr<_Tp>::shared_ptr(shared_ptr<_Yp>&& __r,
typename enable_if<__compatible_with<_Yp, element_type>::value, __nat>::type)
_NOEXCEPT
: __ptr_(__r.__ptr_),
__cntrl_(__r.__cntrl_)
{
__r.__ptr_ = nullptr;
__r.__cntrl_ = nullptr;
}
步骤1:当您调用shared_ptr<Yellowdog>(new Yellowdog());
时,首先将调用获取指针的构造函数,在这种情况下,_Tp与_Yp相同。这里还创建了一个控制块,以_Yp*(所以Yellowdog*)作为输入,并将其保存在内部。然后,创建的控制块被保存为shared_ptr 内的虚拟接口(类型已擦除)
第2步:然后在函数返回期间,CCD_ 12被转换为CCD_。在这种情况下,调用shared_ptr<Dog>
的move构造函数,但是,这次_Tp是Dog,_Yp是Yellowdog。因此CCD_ 15正在发生从Yellowdog*到Dog*的隐式转换。但对于__ctrl_
,它只是一个简单的副本,没有任何变化。
步骤3:在销毁过程中,__ctrl_
中的deallocater将被调用,由于它从一开始就保存了Yellowdog*,因此它可以直接调用其析构函数,进而调用其父(Dog)析构函数。因此,在这种情况下,Dog不需要虚拟析构函数。