使用智能指针错误导致内存泄漏_c++智能指针

本文介绍了C++中的智能指针,包括shared_ptr、unique_ptr和weak_ptr的工作原理和使用方法,着重讨论了如何通过智能指针防止内存泄漏,特别是针对循环引用的问题。
摘要由CSDN通过智能技术生成

a383c97816ff49b61587f3e564a38d88.png

概念

智能指针不是一个指针,它其实是一个对象。它是通过C++的RAII机制实现的。主要是利用C++中对象在释放的时候,会自动调用析构函数这一特性。

所以,当智能指针对象释放的时候,在智能指针对象的析构函数中来释放其管理的内存资源。这样,开发人员就不需要手动去释放已经分配的内存空间。

C++17标准之后,C++标准中还有三种智能指针:shared_ptr、unique_ptr、weak_ptr。下面我们将一一介绍。

智能指针雏形

智能指针雏形代码:

template < typename T>
class SmallSmartPtr {
public: 
 explicit SmallSmartPtr(T* ptr = nullptr)
  : ptr_(ptr) 
 {} 
 ~SmallSmartPtr()
 { 
  delete ptr_; 
  }

 T* get() const { return ptr_; }
private:
 T* ptr_;
};

测试代码:

class Test
{
public:
 Test()
 {
  cout << "Test()" << endl;
 }

 ~Test()
 {
  cout << "~Test()" << endl;
 }
};

int main()
{
  {
   SmallSmartPtr<Test> t(new Test());
 }
 system("pause");
}

测试结果:

ad14358406f01e83f97898ddc0059b75.png

shared_ptr原理介绍

shared_ptr是智能指针的一种,不仅通过RAII机制来管理内存资源,还引入了引用计数来解决当多个智能指针指向同一块内存空间的时候,何时释放这块内存空间的问题。也就是说,同一时刻可以有多个shared_ptr拥有一块内存空间的所有权,当最后一个shared_ptr被销毁时,这块内存空间的引用计数为0时,这块内存空间将被释放。

shared_ptr对象有两个指针,一个是指向管理的内存空间,一个是指向内存控制块,内存控制块中包含引用计数和其他的一些信息(删除器和分配器)。

代码示例:

shared_ptr<Object> t1(new Object());
shared_ptr<Object> t2 = t1;

用图表示如下:

44cddeae6ed915272fbfe66c2af442dc.png

t1释放的时候,引用计数减一,然后释放t1的内存空间,如下:

249bf65f317f7f072aa394a66fba7dd7.png

当t2释放的时候,引用计数会再减一,这时引用计数就会变成0,这时就会释放Object的内存空间和内存控制块的空间,同时t2对象的空间也会被释放。

shared_ptr使用方法

1)初始化

//通过构造函数初始化
shared_ptr<Test> t(new Test());

//使用make_shared来初始化智能指针
shared_ptr<Test> t = make_shared<Test>();

注意:不要使用裸指针进行初始化 如:

//尽量不要使用裸指针来初始化智能指针
Test* pTest = new Test();
shared_ptr<Test> t(pTest);

因为使用裸指针初始化智能指针,容易导致多次使用同一个裸指针对多个智能对象进行初始化。这样就会导致两个智能指针在销毁的时候会去释放同一片内存空间。会造成程序异常崩溃。 如:

Test* pTest = new Test();
shared_ptr<Test> t(pTest);
    
//t1释放的时候会导致程序异常
shared_ptr<Test> t1(pTest);

2)支持拷贝构造、赋值

shared_ptr<Test> t(new Test());
shared_ptr<Test> t1(t);
t1 = t;

3)获取原始指针

shared_ptr<Test> t = make_shared<Test>();
Test* pTest = t.get();

unique_ptr

unique_ptr是独占型智能指针。独占性,就是不允许多个智能指针指向同一块内存空间。也不支持拷贝、赋值,即不能通过赋值将一个unique_ptr赋值给另一个unique_ptr。

1)初始化

//通过构造函数初始化
unique_ptr<Test> t(new Test());

//使用make_unique来初始化智能指针
unique_ptr<Test> t = make_unique<Test>();

2)获取原始指针

unique_ptr<Test> t(new Test());
Test* pTest = t.get();

3)支持所有权转移

unique_ptr不支持拷贝、赋值,但支持所有权转移,即智能指针将当前所指的内存空间的所有权交给另一个智能指针。被管理的内存空间永远只有一个智能指针指向它。

unique_ptr<Test> t(new Test());
unique_ptr<Test> t1 = move(t);

weak_ptr

weak_ptr和shared_ptr、unique_ptr不同,weak_ptr不能单独作为智能指针使用。weak_ptr是用来辅助shared_ptr来解决循环引用的问题。

先看一个示例:

class Test;
class Object
{
public:
 Object()
 {
  cout << "Object()" << endl;
 }

 ~Object()
 {
  cout << "~Object()" << endl;
 }

 shared_ptr<Test> m_pTest;
};

class Test
{
public:
 Test()
 {
  cout << "Test()" << endl;
 }

 ~Test()
 {
  cout << "~Test()" << endl;
 }

 shared_ptr<Object> m_pObject;
};

int main()
{
 {
  shared_ptr<Object> t1(new Object());
  shared_ptr<Test> t2(new Test());
  t1->m_pTest = t2;
  t2->m_pObject = t1;
 }//离开作用域,释放t1、t2
 system("pause");
}

执行结果如下:

606cf138fad9600fee78ee65128668f5.png

发现问题没?为什么没有调用两个对象的析构函数?,也就是说,两个智能指针释放了,但是管理的内存空间没有被释放,那不就是内存泄漏吗,我们看下这一切是怎么发生的:

这个示例的重点是,Test和Object对象中各有一个智能指针类型的成员,而且这两个智能指针指向的都是对方的内存空间。

9e1d1ead7a4b366cceedaebff70d53ec.png

如图所示,每块内存空间都有两个智能指针指向。当t1释放的时候,释放t1对象的内存空间,Test的内存块引用计数减一:

515d912de8dbbb5e0167667bf659bd63.png

同理,t2对象释放的时候,释放t2对象的内存空间,Object的内存块引用计数减一:

5bbab14064d3868671291936f7f408d2.png

此时,已经没有智能指针管理这块内存空间,但是这两块内存空间还未被释放,这个时候,就造成了内存泄漏。

问题的根因是,Test和Object内部的成员指针指向对方的时候,也造成了引用计数加1。

要解决这个问题就需要用到两个东西:weak_ptr和弱引用计数。

也就是说,内存控制块中有一个特殊的引用计数,叫弱引用计数。就是上图中的weak count。为区分,也为了方便表达,我们把原来的引用计数叫强引用计数 。

weak_ptr指向一个对象,不会造成这个对象的内存控制块中的强引用计数加1,只会让弱引用计数加1。而内存空间什么时候释放,是取决于对应的强引用计数什么时候变成0。所以,当t1、t2都被释放之后,两个对象的强引用计数都会变成0。所以,内存空间会被释放。

这个示例的重点是,Test和Object对象中各有一个智能指针类型的成员,而且这两个智能指针指向的都是对方的内存空间。

class Test;
class Object
{
public:
 Object()
 {
  cout << "Object()" << endl;
 }

 ~Object()
 {
  cout << "~Object()" << endl;
 }
//修改成weak_ptr
 weak_ptr<Test> m_pTest;
};

class Test
{
public:
 Test()
 {
  cout << "Test()" << endl;
 }

 ~Test()
 {
  cout << "~Test()" << endl;
 }
//修改成weak_ptr
 weak_ptr<Object> m_pObject;
};

int main()
{
 {
  shared_ptr<Object> t1(new Object());
  shared_ptr<Test> t2(new Test());
  t1->m_pTest = t2;
  t2->m_pObject = t1;
 }
 system("pause");
}

运行结果:

2793a05b105fcae769d4c91f9c57c51b.png

写在最后

C++11中其实有四种智能指针:auto_ptr、shared_ptr、unique_ptr、weak_ptr。但在C++17中已经将auto_ptr从标准中移除。所以本篇文章没有展开介绍。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值