首先我们知道,当一个父类指针指向一个子类对象的时候,如果析构函数为非虚函数的话那么此时对象只会释放父类对象的内存,而子类对象部分就会放生内存泄漏,换句话说就是只会调用父类的构造函数不会调用子类的构造函数。原因很简单,你通过一个父类指针指向了一个子类对象,在调用析构函数的时候此时指针类型为父类所以仅仅调用了父类的析构函数,但是如果析构函数是虚函数,情况就不一样了,虚函数是有多态性质的,当调用析构函数时,在虚函数表中就会存在子类的虚析构函数而不是父类的,此时会调用子类的析构函数。调用子类析构函数的后,调用父类的析构函数,这样就回到了正常的释放内存的步骤了。
以上是裸指针的释放内存的行为,下面我们要说的是当父类的智能指针指向了子类的智能指针会怎样。首先看例子:
//
// Created by dguco on 18-6-18.
//
#include <iostream>
#include <memory>
using namespace std;
class Parent
{
public:
Parent()
{
a = 100;
}
/*virtual*/ ~Parent()
{
printf("~Parent\n");
}
public:
virtual void Say()
{
printf("I am Parent\n");
}
int a;
};
class Child: public Parent
{
public:
/*virtual*/~Child()
{
printf("~Child\n");
}
public:
virtual void Say()
{
printf("I am Child\n");
}
};
int main()
{
{
shared_ptr<Parent> sharedPtr = std::make_shared<Child>() /*= std::make_shared<Child>()*/;
sharedPtr->Say();
}
printf("---------------------------------------------------\n");
Parent *child = new Child;
child->Say();
delete child;
child = NULL;
return 0;
}
运行结果很有意思,使用裸指针的时候发生了内存泄漏,但是使用智能指针的时候并没有,what happened ? 其实仔细看很简单,shared_ptr<Parent> sharedPtr = std::make_shared<Child>(),首先我们构造了一个模板类型为子类的智能指针对象,然后用这个对象为参数构造了模板类型为父类的对象。调用下面的构造函数,这里Tp就是Parent,Tp1是Child。
template<typename _Tp1, typename = _Convertible<_Tp1*>>
shared_ptr(const shared_ptr<_Tp1>& __r) noexcept
: __shared_ptr<_Tp>(__r) { }
接下来看父类:
template<typename _Tp1>
explicit __shared_ptr(_Tp1* __p)
: _M_ptr(__p), _M_refcount(__p)
{
__glibcxx_function_requires(_ConvertibleConcept<_Tp1*, _Tp*>)
static_assert( !is_void<_Tp1>::value, "incomplete type" );
static_assert( sizeof(_Tp1) > 0, "incomplete type" );
__enable_shared_from_this_helper(_M_refcount, __p, __p);
}
首先这是一个模板函数,我们的传入的模板类型为Tp1即Child类型,shared_ptr模板仅仅持有了对象指针,真正掌握指针资源生死大权的是_M_refcount变量。
template<typename _Ptr>
explicit
__shared_count(_Ptr __p) : _M_pi(0)
{
__try
{
_M_pi = new _Sp_counted_ptr<_Ptr, _Lp>(__p);
}
__catch(...)
{
delete __p;
__throw_exception_again;
}
}
这里同样是一个模板函数,Ptr即为我们传入的Child*类型,当shared_ptr发生拷贝或者析构的时候,相应的增加过减少_Sp_counted_ptr持有的原子类型的引用技术,当引用数减为0时就释放持有的对象,此时我们传入的时Child*类型,所以当释放内存的时候实际操作为delete (std::make_shared<Child>())持有的对象指针,即Child*类型的指针。
_Tp* _M_ptr; // Contained pointer.
__shared_count<_Lp> _M_refcount; // Reference counter.
这里要注意,一个shared_ptr实际上有俩个成员,一个_M_ptr,一个_M_refcount,第一个用来使用模板指向的对象,模板类重重载了->操作,此时调用的是_M_ptr->,shared_ptr<Parent> sharedPtr = std::make_shared<Child>(),此时sharedPtr 中的_M_ptr是_Tp*类型即Parent,而_M_refcount中同样持有一个对象指针,他的类型为实际对象的类型即Child*类型,这也是智能指针为什么在表现多态的同时,有正确释放掉父类了子类内存的关键(我们并没有把析构函数写为虚函数)。