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 不需要虚拟析构函数。
典型的工厂设计模式需要基 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 不需要虚拟析构函数。