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

c++ shared_ptr in polymorphism without virtual destructor

典型的工厂设计模式需要基 class 声明虚拟析构函数,但这实际上可以使用 shared_ptr.

避免
#include <iostream>
#include <memory>
#include <cstdio>

using namespace std;

class Dog {
  public:
  ~Dog() { cout << "dog destroyed\n"; }
};

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前的virtual关键字?

发生这种情况是因为 shared_ptr 将类型擦除的删除器存储在控制块中,该块是在创建第一个 shared_ptr 时创建的。在你的例子中 shared_ptr 是为 yellow dog 创建的,删除器是调用 yellow-dog 的析构函数。

当您将一个 shared_ptr 复制(或复制构造)到另一个时,它们 共享 相同的控制块,并且新的共享 ptr 将从调用删除器原始的 - 将调用 yellowdog 析构函数的那个​​。

但是,它并没有真正使 class 多态并适合工厂实现 - class 中的任何其他非虚拟函数将基于 [=10] 的静态类型被调用=],并且您不希望在多态 classes.

中使用它

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;
}

第一步: 当你调用 shared_ptr<Yellowdog>(new Yellowdog()); 时,首先会调用获取指针的构造函数,在这种情况下 _Tp 与 _Yp 相同。还创建了一个控制块,将 _Yp*(所以 Yellowdog*)作为输入,并将其保存在里面。然后将创建的控制块作为其虚拟接口(类型擦除)保存在 shared_ptr

第 2 步: 然后在函数 return 期间, shared_ptr<Yellowdog> 被转换为 shared_ptr<Dog>。 在这种情况下,shared_ptr<Dog> 的移动构造函数被调用,但是,这次_Tp 是 Dog,_Yp 是 Yellowdog。因此 __ptr_ 发生了从 Yellowdog* 到 Dog* 的隐式转换。但是对于 __ctrl_ 它是一个简单的副本,没有任何变化。

第 3 步: 在销毁过程中,来自 __ctrl_ 的 deallocator 将被调用,并且由于它从一开始就保存了 Yellowdog*,因此它可以直接调用其析构函数,进而调用其父(Dog)析构函数。 因此在这种情况下 Dog 不需要虚拟析构函数。