C++智能指针总结三——shared_ptr与weak_ptr

1.往期回顾

C++智能指针总结一——auto_ptr

C++智能指针总结二——unique_ptr

2.为什么要使用shared_ptr

这是因为,无论是auto_ptr还是unique_ptr都是排它型的,即只允许一个智能指针对象引用控制一块动态内存。那如果我们要使用多个指向同一块动态内存的指针时,显然auto_ptr与unique_ptr都不能做到。因此,为了实现多个智能指针引用同一块动态内存的目的,C++标准新增了shared_ptr。

3.shared_ptr的使用

1.shared_ptr原理

shared_ptr内部实现了一个计数器,用于记录引用同一块动态内存的智能指针数量,一旦有新的智能指针指向了已被引用的动态内存,引用计数加一。一旦有智能指针生命周期到了调用析构函数销毁时,引用计数减一,如果检测到此时引用计数为零,则销毁智能指针的同时释放动态内存。

看一个例子:

#include <Windows.h>
#include <iostream>
#include <string>
#include <vector>
#include <memory>

using namespace std;

class DEMO {

public:

    DEMO(int data) {

        this->data = data;
        cout << "创建text" << endl;
        cout << "data=" << data << endl;
    }

    ~DEMO() {
        cout << "析构text" << data << endl;
    }

private:
    int data;
};


int main(void) {

    {
        shared_ptr<DEMO> ptr1(new DEMO(1));       //引用计数加一
        {
            shared_ptr<DEMO> ptr2(ptr1);          //引用计数加一      
            {
                shared_ptr<DEMO> ptr3(ptr2);      //引用计数加一
                {
                    shared_ptr<DEMO> ptr4(ptr3);  //引用计数加一
                    cout << "ptr4.count=" << ptr4.use_count() << endl;
                }

                cout << "ptr3.count=" << ptr3.use_count() << endl;   //引用计数减一     
            }

            cout << "ptr2.count=" << ptr2.use_count() << endl;      //引用计数减一     
        }

        cout << "ptr1.count=" << ptr1.use_count() << endl;          //引用计数减一     
    }                                                               //引用计数减一     


    system("pause");

    return 0;
}

结果:

在这里插入图片描述
shared_ptr ptr2(ptr1),即使用ptr1初始化ptr2 。

.use_count()方法可以返回智能指针的引用计数。

开始一共有四个指针指向同一块动态内存,所以最开始引用变量是4,而后随着指针生命周期的结束,引用变量逐渐减一,直至引用变量减为0,释放动态内存,执行DEMO对象的析构函数。

2.shared_ptr的使用

主要补充一些shared_ptr新增的API,至于与auto_ptr,unique_ptr一样的方法,就不赘述了。

1.构造

#include <Windows.h>
#include <iostream>
#include <string>
#include <vector>
#include <memory>

using namespace std;

int main(void) {

    shared_ptr<int> ptr1;         //初始化为空的shared_ptr,可以指向int类型的对象
    shared_ptr<int []> ptr2;      //初始化为空的shared_ptr,可以指向int类型的数组
    shared_ptr<int[]> ptr3(new int[5]{ 1,2,3,4,5 });  //使用参数列表初始化动态数组,C++17后支持
    
    system("pause");

    return 0;
}

2.使用make_shared 初始化对象

#include <Windows.h>
#include <iostream>
#include <string>
#include <vector>
#include <memory>

using namespace std;

int main(void) {

    shared_ptr<int> ptr1 = make_shared<int>(5);

    system("pause");

    return 0;
}

推荐使用make_shared初始化对象,因为效率更高。

使用shared_ptr初始化与赋值要明白引用计数的变化,其实只要看有几个智能指针对象指向了同一块内存,引用计数就是几。

4.使用shared_ptr的陷阱

先看一段代码:

#include <Windows.h>
#include <iostream>
#include <string>
#include <vector>
#include <memory>

using namespace std;

class GRIL;
//创建BOY类
class BOY {

public:

	BOY() {
		cout << "执行BOY类的构造方法" << endl;
	}

	~BOY() {
		cout << "执行BOY类的析构方法" << endl;
	}

	void GetGril(shared_ptr<GRIL>& gril_ptr) {   //BOY获取GRIL方法
		gril = gril_ptr;
	}

private:

	shared_ptr<GRIL> gril;     //BOY类里的shared_ptr指针
};


//创建GRIL类
class GRIL {

public:

	GRIL() {
		cout << "执行GRIL类的构造方法" << endl;
	}

	~GRIL() {
		cout << "执行GRIL类的析构方法" << endl;
	}

	void GetBoy(shared_ptr<BOY>& boy_ptr) {     //GRIL类获取BOY方法
		boy = boy_ptr;
	}

private:

	shared_ptr<BOY> boy;      //GRIL类里的shared_ptr指针
};


int main(void) {

	{
		shared_ptr<BOY> ptr1(new BOY);      //分配一个boy类对象
		shared_ptr<GRIL> ptr2(new GRIL);   //分配一个gril类对象

		ptr1->GetGril(ptr2);
		ptr2->GetBoy(ptr1);
	}


	system("pause");

	return 0;
}

执行结果为:

在这里插入图片描述

从结果可以看到,动态分配的BOY与GRIL类对象并未被释放,这是为什么呢?

其实这是由于BOY类与GRIL类对象里的shared_ptr指针相互引用造成的。看一张图:

在这里插入图片描述动态创建完BOY类与GRIL类的对象后,它们各自的智能指针的引用计数为一,又因为BOY类与GRIL类里各有一个指向对方的shared_ptr指针,执行完它们各自的.GetBoy()与.GetGril()方法后。会形成如上图的关系。

当shared_ptr1与shared_ptr2的生命周期到了,被销毁后,会变成如下图的关系:

在这里插入图片描述
此时引用计数为一不为零,所以任何一个动态内存都无法释放,其中一个无法释放,就导致另一个无法释放,就造成了类似于死锁的情况。两个都不能释放。就造成了内存泄漏。

再补充一种无法释放的情景:

#include <Windows.h>
#include <iostream>
#include <string>
#include <vector>
#include <memory>

using namespace std;

//创建BOY类
class BOY {

public:

	shared_ptr<BOY> boy1;

	BOY() {
		cout << "执行BOY类的构造方法" << endl;
	}

	~BOY() {
		cout << "执行BOY类的析构方法" << endl;
	}
};

int main(void) {
	
	shared_ptr<BOY> ptr1(new BOY);
	ptr1->boy1 = ptr1;

	system("pause");

	return 0;
}

结果:

在这里插入图片描述

这种情况同样会造成内存泄漏。

总结来说无法释放内存的原因是,由于对象是动态分配的,而对象本身又含有shared_ptr指针,释放对象需要shared_ptr的释放使引用计数减为零,而shared_ptr的释放又需要对象的释放,两者互相等待对方先释放,往往是两者都无法释放。

5.“死锁”的解除——使用weak_ptr

weak_ptr指针——弱指针,它是为了解决shared_ptr的弊端而被设计出来辅助shared_ptr完成工作的指针。它不能使用 * 与 -> 运算符。但它可以通过lock 获得一个可用的 shared_ptr 对象。weak_ptr不会引起引用计数的变化。除非它通过lock 获得一个可用的 shared_ptr 对象,这时引用计数会加一,shared_ptr被销毁后,引用计数会减一。

看一个例子:

#include <Windows.h>
#include <iostream>
#include <string>
#include <vector>
#include <memory>

using namespace std;

//创建BOY类
class BOY {

public:

	weak_ptr<BOY> boy1;

	BOY() {
		cout << "执行BOY类的构造方法" << endl;
	}

	~BOY() {
		cout << "执行BOY类的析构方法" << endl;
	}

	void demo() {

		cout << "count=" << boy1.use_count() << endl;
		shared_ptr<BOY> tmp = boy1.lock();     //当需要使用引用动态内存时,可使用.lock()方法构造一个临时的shared_ptr,因为是临时变量,用完后会析构掉。
		cout << "count=" << tmp.use_count() << endl;
	}
};

int main(void) {

	shared_ptr<BOY> ptr1(new BOY);
	ptr1->boy1 = ptr1;
	ptr1->demo();

	system("pause");

	return 0;
}

结果:

在这里插入图片描述

当weak_ptr需要使用引用动态内存时,可使用.lock()方法构造一个临时的shared_ptr,此时引用变量会加一。因为是临时变量,用完后会析构掉。计数减一。使用weak_ptr可以避免shared_ptr导致的动态内存无法释放的问题。

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值