1、现象
#include <memory>
#include <iostream>
using namespace std;
class A
{
public:
~A() { cout << "A" << endl; }
};
class B : public A
{
public:
~B() { cout << "B" << endl; }
};
int main()
{
shared_ptr<A> ptr(new B);
return 0;
}
为什么输出结果是
B
A
在父类没有虚析构函数的情况下,通过父类指针析构子类对象。为什么智能指针可以做到这个
2、实现
std::shared_ptr 在创建时会保存删除器(deleter),该删除器在 std::shared_ptr 被销毁时调用。默认情况下,std::shared_ptr 使用 delete 操作符作为删除器。因此,当你创建 std::shared_ptr 对象时,实际上会保存一个指向派生类对象的指针,并在销毁时正确地调用派生类的析构函数。
template <typename T>
class MySharedV2
{
public:
template <typename T2>
MySharedV2(T2 *p)
{
data_ = p;
deleter_ = [p](){ delete p;};
}
~MySharedV2()
{
deleter_();
}
T* operator->()
{
return data_;
}
private:
std::function<void()> deleter_;
T* data_ = nullptr;
};
3、STL源代码分析
- 1、构造
在 shared_ptr 的构造过程中,模板构造函数会接受一个指向派生类对象的原始指针,并将其传递给内部的 _M_ptr 成员,同时构造一个 _M_refcount 对象以管理引用计数和删除器。
template<typename _Yp, typename = _Constructible<_Yp*>>
explicit
shared_ptr(_Yp* __p) : __shared_ptr<_Tp>(__p) { }
- 2、内部实现
__shared_ptr 类的构造函数会初始化指针和引用计数对象,并为指针类型设置删除器。
template<typename _Yp, typename = _SafeConv<_Yp>>
explicit
__shared_ptr(_Yp* __p)
: _M_ptr(__p), _M_refcount(__p, typename is_array<_Tp>::type())
{
static_assert( !is_void<_Yp>::value, "incomplete type" );
static_assert( sizeof(_Yp) > 0, "incomplete type" );
_M_enable_shared_from_this_with(__p);
}
在这段代码中:
_M_ptr
被初始化为指向传入的对象指针。
_M_refcount
被初始化为一个包含指针和删除器的对象。
static_assert
确保传入的指针类型是完整类型,不能是 void。
template<typename _Ptr, _Lock_policy _Lp>
class _Sp_counted_ptr final : public _Sp_counted_base<_Lp>
{
public:
explicit
_Sp_counted_ptr(_Ptr __p) noexcept
: _M_ptr(__p) { }
virtual void
_M_dispose() noexcept
{ delete _M_ptr; }
private:
_Ptr _M_ptr;
};
当 delete _M_ptr 被调用时,_M_ptr 的实际类型是 B*,即使 _M_ptr 被存储为 A* 类型的指针。这是因为 std::shared_ptr 是模板类,它在实例化时知道实际的类型(即 B*),并将这个类型传递给内部的删除器。
模板实例化的行为
具体来说,在模板实例化过程中,shared_ptr 知道传入的是 B* 类型的指针。虽然存储的是 A* 类型的指针,但是当 delete 操作符被调用时,C++ 会确保调用的是指针实际类型的析构函数(即 B 的析构函数)。然后,B 的析构函数会自动调用 A 的析构函数。这是C++对象模型的一部分,保证了对象的完整销毁过程。