c++在没有虚拟析构函数的多态性中共享ptr



典型的工厂设计模式要求基类声明虚拟析构函数,但使用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不需要虚拟析构函数。

最新更新