栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

C++智能指针总结三——shared

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

C++智能指针总结三——shared

C++智能指针总结三——shared_ptr与weak_ptr 【奇牛学院】
    • 1.往期回顾
    • 2.为什么要使用shared_ptr
    • 3.shared_ptr的使用
      • 1.shared_ptr原理
      • 2.shared_ptr的使用
    • 4.使用shared_ptr的陷阱
    • 5.“死锁”的解除——使用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 
#include 
#include 
#include 
#include 

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 ptr1(new DEMO(1));       //引用计数加一
        {
            shared_ptr ptr2(ptr1);          //引用计数加一      
            {
                shared_ptr ptr3(ptr2);      //引用计数加一
                {
                    shared_ptr 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 
#include 
#include 
#include 
#include 

using namespace std;

int main(void) {

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

    return 0;
}

2.使用make_shared 初始化对象

#include 
#include 
#include 
#include 
#include 

using namespace std;

int main(void) {

    shared_ptr ptr1 = make_shared(5);

    system("pause");

    return 0;
}

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

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

4.使用shared_ptr的陷阱

先看一段代码:

#include 
#include 
#include 
#include 
#include 

using namespace std;

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

public:

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

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

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

private:

	shared_ptr gril;     //BOY类里的shared_ptr指针
};


//创建GRIL类
class GRIL {

public:

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

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

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

private:

	shared_ptr boy;      //GRIL类里的shared_ptr指针
};


int main(void) {

	{
		shared_ptr ptr1(new BOY);      //分配一个boy类对象
		shared_ptr 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 
#include 
#include 
#include 
#include 

using namespace std;

//创建BOY类
class BOY {

public:

	shared_ptr boy1;

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

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

int main(void) {
	
	shared_ptr 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 
#include 
#include 
#include 
#include 

using namespace std;

//创建BOY类
class BOY {

public:

	weak_ptr boy1;

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

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

	void demo() {

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

int main(void) {

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

	system("pause");

	return 0;
}

结果:

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

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/629308.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号