智能指针学习笔记

智能指针

1. 普通指针的不足

​ new 和 new[] 的内存需要用 delete 和 delete[] 释放。(堆区的内存一定要手工释放,否则会发生内存的泄露。)

2. 普通指针的释放

​ 1)类内的指针,在析构函数中释放

​ 2)c++内置的数据类型,手工的delete

​ 3)new出来的类,delete 这个对象时,才会调用析构函数(用new创建对象时,调用构造函数;用delete销毁对象时,调用析构函数)

3. 智能指针的设计思路

  • 智能指针是类模板,在栈上创建智能指针对象。

  • 把普通指针交给智能指针对象。

  • 智能指针对象过期时,调用析构函数释放普通指针的内存。

4.智能指针的类型

  1. auto_ptr 是C++98的标准,C++17已弃用。
  2. unique_ptr,share_ptr 和 weak_ptr 是C++ 11 标准的。

5. unique_ptr

在C++中,多个指针可以指向同一个对象。(智能指针就是类,类中有一个成员,管理着原始指针)

unique_ptr 独享它指向的对象,即对象在同一时间只被一个unique_ptr管理;当这个unique_ptr被销毁时,指向的对象也被销毁。

包含头文件#include

template <typename T, typename D = default_delete<T>>
class unique_ptr
{
public:
	explicit unique_ptr(pointer p) noexcept;	// 不可用于转换函数。
	~unique_ptr() noexcept;    
	T& operator*() const;            // 重载*操作符。
	T* operator->() const noexcept;  // 重载->操作符。
	unique_ptr(const unique_ptr &) = delete;   // 禁用拷贝构造函数。
	unique_ptr& operator=(const unique_ptr &) = delete;  // 禁用赋值函数。
	unique_ptr(unique_ptr &&) noexcept;	  // 右值引用。
	unique_ptr& operator=(unique_ptr &&) noexcept;  // 右值引用。
	// ...
private:
	pointer ptr;  // 内置的指针。
};

// 函数后面有 =delete,表示这个函数已被删除

第一个模板参数T:指针指向的数据类型

第二个模板参数D:指定删除器,缺省用delete 释放资源。

例子:

#include <iostream>
#include <memory>
#include <vector>
using namespace std;

class AA {
public:
	string m_name;
	AA() { cout <<m_name<< "	调用了构造函数AA()" << endl; }
	AA(const string& name) :m_name(name) { cout << m_name << "	调用了构造函数AA(const string& name)" << endl; }
	~AA() { cout << "调用了析构函数~AA()	" << m_name << endl; }
};

int main(void)
{
	AA* p = new AA("西施");
	unique_ptr<AA> pu1(p);	
	// 模板参数AA表示需要管理的普通指针的基类型(指向的对象的数据类型)是AA,p表示被管理的指针,p指向了new出来的对象的地址
	// 间接的意思是让智能指针pu1来管理对象
	// 智能指针是类,有析构函数,在它的析构函数中使用了delete语句
	// 智能指针重载了 * 和 -> 操作符,像使用普通指针一样使用智能指针

	cout << "m_name=" << (*pu1).m_name << endl;
	cout << "m_name=" << pu1->m_name << endl;
	cout << "m_name=" << (*p).m_name << endl;
	cout << "m_name=" << p->m_name << endl;
	/*delete p;*/

	return 0;
}

unique_ptr pu1§;
// 模板参数AA表示需要管理的普通指针的基类型(指向的对象的数据类型)是AA,p表示被管理的指针,p指向了new出来的对象的地址
// 间接的意思是让智能指针对象pu1来管理p指向的对象
// 智能指针是类,有析构函数,在它的析构函数中使用了delete语句

5.1 基本用法

5.1.1 初始化

方法一:

​ **unique_ptr pu1(new AA(“西施”)); ** // 分配内存并初始化

方法二:

unique_ptr pu1 = make_unique(“西施”); // c++ 14标准

方法三:

AA p = new AA(“西施”);*

unique_ptr pu1§; // 用已存在的地址初始化

注意:方法一和方法三其实是一样的,unique_ptr的构造函数的参数是指针,new返回的是地址,p中存放的也是对象的地址,

//TODO: 错误
	unique_ptr<AA> pu3 = p;		//ERROR,不能把普通指针直接赋值给智能指针(智能指针的构造函数是explicit修饰,不能用于转换函数)
	unique_ptr<AA> pu4 = new AA("西施"); //error, 不能把普通指针直接赋值给智能指针(
	
	unique_ptr <AA> pu5 = pu1;	// error,禁用拷贝构造函数,不能用其他智能指针拷贝构造

	unique_ptr<AA> pu6;
	pu6 = pu1;					// error ,不能用 = 对unique_ptr进行赋值
	//! unique_ptr的设计目标是独享对象,即一个unique_ptr对象只对一个资源负责。
	//! 如果unique_ptr对象允许复制,那么会出现多个unique_ptr对象指向同一块内存的情况,当其中一个unique_ptr对象过期的时候,释放内存;其他的unique_ptr对象过期的时候,又会释放内存,造成的结果是对同一块内存释放多次,就成了操作野指针。
	//TODO: 错误
5.1.2 使用方法
  • 智能指针重载了 * 和 -> 操作符,可以像使用指针一样使用 unique_ptr。
  • 不支持普通的拷贝和赋值

​ AA* p = new AA(“西施”);

unique_ptr pu2 = p; // 错误,不能把普通指针直接赋给智能指针。

unique_ptr pu3 = new AA(“西施”); // 错误,不能把普通指针直接赋给智能指针。

unique_ptr pu2 = pu1; // 错误,不能用其它unique_ptr拷贝构造。

unique_ptr pu3;

pu3 = pu1; // 错误,不能用=对unique_ptr进行赋值。

  • 不要用同一个裸指针初始化多个 unique_ptr对象。
  1. 裸指针就是普通指针,也叫原始指针
  2. 在实际开发中,如果使用了智能指针,最好不要使用裸指针
  • get() 方法返回裸指针
AA* p = new AA("西施");		// 定义原始指针p,分配内存
	unique_ptr<AA> pu1(p);		// 创建智能指针对象pu1,用于管理原始指针p

	cout << "裸指针的值是:         " << p << endl;
	cout << "pu1输出的值是:        " << pu1 << endl;
	cout << "pu1.get()输出的结果是:" << pu1.get() << endl;
	cout << "pu1的地址是:          " << &pu1 << endl;
	//!智能指针就是类,类中有一个成员,管理着原始指针

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

有一个函数重载了<<运算符,是unique_ptr模板类的友元,可以访问unique_ptr对象中的原始指针。

  • 不要用 unique_ptr 管理不是new 分配的内存。
  • 用于函数的参数

​ 1)传引用(不能传值,因为unique_ptr没有拷贝构造函数)

void func(unique_ptr<AA>& pp)
{
	cout << "m_name=" << pp->m_name << endl;
}

int main(void)
{
	unique_ptr<AA> pu1(new AA("西施"));		// 创建智能指针对象pu1,用于管理原始指针p

	func(pu1);
}

​ 2)裸指针

  • 不支持指针的运算(+,-,++,–)

5.2 更多技巧

  • 将一个 unique_ptr 赋给另一个,如果源 unique_ptr是一个临时右值,编译器允许这样做;如果源 unique_ptr将存在一段时间,编译器禁止这样做。一般用于函数的返回值。
unique_ptr<AA> p0;
p0 = unique_ptr<AA> (new AA("西瓜"));
#include <iostream>
#include <vector>
#include <memory>
using namespace std;

class AA {
public:
	string m_name;
	AA() { cout << "调用了构造函数AA()" << endl; }
	AA(const string& name) :m_name(name) { cout << "调用了构造函数AA(const dtring& name):" << m_name << endl; }
	~AA() { cout << "调用了析构函数~AA():" << m_name << endl; }

};


unique_ptr<AA> func()
{
	unique_ptr<AA> pp(new AA("西施3"));
	return pp;
}

int main(void)
{
	unique_ptr<AA> pu1(new AA("西施1"));
	unique_ptr<AA> pu2;
	pu2 = unique_ptr<AA> (new AA("西施2"));  //用匿名对象给pu2赋值,匿名对象在这行代码之后就不存在了

	cout << "调用func()之前" << endl;
	pu2 = func();	// 用函数的返回值赋值
	cout << "调用func()之后" << endl;

	// 智能指针对象已经获得了一个资源的管理权(西施2),再把func()函数中分配的资源(西施3)也交给pu2,
	// pu2不可能管理多个资源,所以先释放 西施2,再接受西施3
}

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 用nullptr给 unique_ptr 赋值将释放对象,空的 unique_ptr == nullptr
unique_ptr<AA> pu(new AA("西施"));

	cout << "赋值前" << endl;
	if (pu != nullptr) {
		cout << "pu不是空的" << endl;
	}
	pu = nullptr;
	cout << "赋值后" << endl;
	if (pu == nullptr) {
		cout << "pu是空的" << endl;
	}

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • release() 释放对原始指针的控制权,将 unique_ptr 置为空,返回裸指针(普通指针)。(可用于把 unique_ptr传递给子函数,子函数将负责释放对象)

  • std::move() 可以转移对原始指针的控制权。(可用于把 unique_ptr 传递给子函数,子函数形参也是 unique_ptr )

#include <iostream>
#include <vector>
#include <memory>
using namespace std;

class AA {
public:
	string m_name;
	AA() { cout << "调用了构造函数AA()" << endl; }
	AA(const string& name) :m_name(name) { cout << "调用了构造函数AA(const dtring& name):" << m_name << endl; }
	~AA() { cout << "调用了析构函数~AA():" << m_name << endl; }

};


unique_ptr<AA> func()
{
	unique_ptr<AA> pp(new AA("西施3"));
	return pp;
}

// 函数func1()需要一个指针,但不对这个指针负责(释放资源的事情,这个函数不管)
void func1(const AA* a)
{
	cout << a->m_name << endl;

}

// 函数func2()需要一个指针,对这个指针负责( 释放资源的事情由func2()管 )
void func2(AA* a)
{
	cout << a->m_name << endl;
	delete a;
}

// 函数func3()需要一个unique_ptr对象,不会对这个unique_ptr对象负责
void func3(const unique_ptr<AA>& a)
{
	cout << a->m_name << endl;
}

// 函数func4()需要一个unique_ptr对象,对这个unique_ptr对象负责
void func4(unique_ptr<AA> a)
{
	cout << a->m_name << endl;
}


int main(void)
{
	unique_ptr<AA> pu(new AA("西施"));


	cout << "开始调用函数" << endl;
	//func1(pu.get()); // 函数func1()需要一个指针,但不对这个指针负责
	
	//func2(pu.release()); // 函数func2()需要一个指针,对这个指针负责( 释放资源的事情由func2()管 )
	/*if (pu == nullptr) {
		cout << "pu是空指针" << endl;
	}*/
	//func3(pu);			// 函数func3()需要一个unique_ptr对象,不会对这个unique_ptr对象负责
	
	func4(move(pu)); // 函数func4()需要一个unique_ptr对象,对这个unique_ptr对象 ( 负责释放资源的事情由func2()管 )

	cout << "调用函数完成" << endl;

}
  • reset()释放对象

声明:

void reset(T _ptr = (T) nullptr );**

例子:

pp.reset(); // 释放pp对象指向的资源对象。

pp.reset(nullptr); // 释放pp对象指向的资源对象

pp.reset(new AA(“bbb”)); // 释放pp指向的资源对象,并同时指向新的对象

  • swap() 交换两个 unique_ptr 的控制权。

void swap(unique_ptr& _Right);

  • unique_ptr 也可像普通指针那样,当指向一个类继承体系的基类对象时,具有多态性质,如同裸指针管理基类对象和派生类对象那样。
#include <iostream>
#include <memory>
using namespace std;

// 多态的应用场景:基类中的成员函数(虚函数)实现基本功能,派生类中重定义虚函数进行扩充功能
// 对普通继承来说,成员变量的地址是静态的,不会出现在内存模型中

// 如果类中没有虚函数,编译的时候,编译器直接将成员函数的地址链接到二进制文件中。
// 如果类中由虚函数,编译的时候,编译器不会把虚函数的地址链接到二进制文件中,
// 在有虚函数的类中,多了一个隐身的成员,即虚函数指针(vfptr),程序在运行的过程中,
// 如果创建了对象,除了给对象的成员分配内存,还会创建一个虚函数表(vftable),
// 用虚函数指针指向虚函数表。
/*
* 在程序中,如果调用的是普通成员函数,编译器将成员函数地址链接到二进制文件中,直接执行函数
* 如果调用的是虚函数,要先查找虚函数表,得到函数的地址,再执行函数
*/

class Hero {
public:
	int viablity;
	int attack;
	virtual void skill1() { cout << "英雄释放了一技能." << endl; }
	virtual void skill2() { cout << "英雄释放了二技能." << endl; }
	virtual void skill3() { cout << "英雄释放了大招." << endl; }
	Hero() :viablity{ 0 }, attack{ 0 } {  }
};

class XS :public Hero {
public:

	void skill1() { cout << "西施释放了一技能." << endl; }
	void skill2() { cout << "西施释放了二技能." << endl; }
	//void skill3() { cout << "西施释放了大招." << endl; }
};

class HX :public Hero {
public:
	void skill1() { cout << "韩信释放了一技能." << endl; }
	void skill2() { cout << "韩信释放了二技能." << endl; }
	void skill3() { cout << "韩信释放了大招." << endl; }
};

class LB :public Hero {
public:
	void skill1() { cout << "李白释放了一技能." << endl; }
	void skill2() { cout << "李白释放了二技能." << endl; }
	void skill3() { cout << "李白释放了大招." << endl; }
};

int main(void)
{
		// 创建基类指针,让它指向派生类,用基类指针调用派生类的成员函数
		int id = 0;
		cout << "请输入英雄(1:西施  2:韩信 3:李白):";
		cin >> id;
		// 1:西施  2:韩信 3:李白
		unique_ptr<Hero> ptr;
	
		if (id == 1) {	
			ptr = unique_ptr<XS>(new XS);
		}
		else if (id == 2) {
			ptr = unique_ptr<HX>(new HX);
		}
		else if (id == 3) {
			//ptr=new LB;
			ptr = unique_ptr<LB>(new LB);
		}

		if (ptr != nullptr) {
			ptr->skill1();
			ptr->skill2();
			ptr->skill3();

		}
		
		return 0;
}
  • unique_ptr 不是绝对安全,如果程序中调用 exit() 退出,全局的 unique_ptr 可以自动释放 ,但是局部的 unique_ptr 无法释放。

exit() 是一个 c/c++ 标准库函数,用于终止程序的执行并返回操作系统。当使用exit() 函数时,程序会立即退出,不再执行后续的代码。在程序退出时,会执行一些清理工作,如关闭文件,释放内存等。同时,exit()函数还可以传递一个整型参数,表示程序退出时的状态码,这个状态码可以被操作系统获取,通常用于指示程序的退出状态。

  • unique_ptr 提供了支持数组的具体化版本。

数组版本的 unique_ptr ,重载了操作符 [] ,操作符[] 返回的是引用,可以作为左值使用。

​ unique_ptr<AA[]> parr1(new AA[2]); // unique_ptr数组
​ // unique_ptr<AA[]> parr1(new AA[2]{string(“西施1”),string(“西施2”)}); // 指定初始化
​ parr1[0].m_name = “西施1”;
​ cout << "parr1[0].m_name= " << parr1[0].m_name << endl;
​ parr1[1].m_name = “西施2”;
​ cout << "parr1[1].m_name= " << parr1[1].m_name << endl;

6. 智能指针 shared_ptr

shared_ptr 共享它指向的对象,多个 shared_ptr 可以指向(关联)相同的对象,在内部采用计数机制来实现。

当新的 shared_ptr 与对象关联时,引用计数增加1。

当shared_ptr 超出作用域是,引用计数减1。当引用计数变为0时,则表示没有任何shared_ptr 与对象关联,则释放该对象。

6.1 基本用法

shared_ptr 的构造函数也是explicit ,但是有拷贝构造函数和赋值函数。

6.1.1 初始化

方法一:

shared_ptr p0(new AA(“西施”)); // 分配内存并初始化。

方法二:(推荐)

shared_ptr p0 = make_shared(“西施”); // C++11标准,效率更高。

shared_ptr pp1=make_shared(); // 数据类型为int。

shared_ptr pp2 = make_shared(); // 数据类型为AA,默认构造函数。

shared_ptr pp3 = make_shared(“西施”); // 数据类型为AA,一个参数的构造函数。

shared_ptr pp4 = make_shared(“西施”,8); // 数据类型为AA,两个参数的构造函数。

方法三:

AA p = new AA(“西施”);*

shared_ptr p0§; // 用已存在的地址初始化。

方法四:

shared_ptr p0(new AA(“西施”));

shared_ptr p1(p0); // 用已存在的shared_ptr初始化,计数加1。

shared_ptr p1=p0; // 用已存在的shared_ptr初始化,计数加1。

6.1.2 使用方法
  1. 智能指针重载了 * 和 -> 操作符,可以像使用指针一样使用 shared_ptr

  2. use_count() 方法返回 引用计数的值

  3. unique() 方法,如果 use_count() 为1,返回true;否则返回 false;

  4. shared_ptr 支持赋值,左值的 shared_ptr的计数器将减1,右值 shared_ptr 的计数器将加1

  5. shared_ptr<AA> pa0 = make_shared<AA>("西施a"); // 初始化西施a
    	
    	shared_ptr<AA> pa1 = pa0;	// 用已存在的shared_ptr拷贝构造,计数加1
    	shared_ptr<AA> pa2 = pa0;	// 用已存在的shared_ptr拷贝构造,计数加1
    	cout << "pa0.use_count()=" << pa0.use_count() << endl;	//值为3
    
    	cout << endl;
    	shared_ptr<AA> pb0 = make_shared<AA>("西施b");	// 初始化西施b
    	shared_ptr<AA> pb1 = pb0;	// 用已存在的shared_ptr拷贝构造,计数加1
    	shared_ptr<AA> pb2 = pb0;	// 用已存在的shared_ptr拷贝构造,计数加1
    	cout << "pb0.use_count()=" << pb0.use_count() << endl;	// 值为3
    	cout << endl;
    
    	pb2 = pa2;	// 赋值,将pb2也指向了西施a,所以指向西施a的shared_ptr对象加1,指向西施b的shared_ptr对象减1
    	/*pb1 = pa2;
    	pb0 = pa2; //! pb0指向西施a后,西施b没有 shared_ptr与之关联, 则释放西施b这个资源 */
    
    	//TODO: 指向资源的指针多了一个,引用计数就加1;指向资源的指针少了一个,引用计数就减1;如果引用计数为0了,就释放该资源。
    	cout << "pa0.use_count()=" << pa0.use_count() << endl;	//值为4
    	cout << "pb0.use_count()=" << pb0.use_count() << endl;	//值为2
    	cout << endl;
    

    指向资源的指针多了一个,引用计数就加1;指向资源的指针少了一个,引用计数就减1;如果引用计数为0了,就释放该资源。

  6. get() 方法返回裸指针

  7. 不要用同一个裸指针初始化多个 shared_ptr

  8. 不要用 shared_ptr 管理不是new分配的内存。

  9. 用于函数的参数

void func(shared_ptr<AA>& pp)
{
	cout << "m_name=" << pp->m_name << endl;
}

int main(void)
{
	shared_ptr<AA> pu1(new AA("西施"));		// 创建智能指针对象pu1,用于管理原始指针p

	func(pu1);
}

10.不支持指针的运算(++,–,+,-)

6.1.3 更多细节
  • 用nullptr给 shared_ptr 赋值将把计数减1;如果引用计数为0,就释放对象;空的shared_ptr == nullptr
  • std::move() 可以转移对原始指针的控制权,还可以将 unique_ptr转移成 shared_ptr
  • reset() 改变与资源的关联关系。

pp.reset(); // 解除与资源的关系,资源的引用计数减1。

pp. reset(new AA(“bbb”)); // 解除与资源的关系,资源的引用计数减1。关联新资源。

  • swap() 交换两个shared_ptr的控制权。

void swap(shared_ptr &_Right);

  • shared_ptr也可象普通指针那样,当指向一个类继承体系的基类对象时,也具有多态性质,如同使用裸指针管理基类对象和派生类对象那样

  • shared_ptr不是绝对安全,如果程序中调用exit()退出,全局的shared_ptr可以自动释放,但局部的shared_ptr无法释放。

  • shared_ptr提供了支持数组的具体化版本。

    数组版本的shared_ptr,重载了操作符[],操作符[]返回的是引用,可以作为左值使用。

    	shared_ptr<AA[]> parr1(new AA[2]);	// shared_ptr数组
    	// shared_ptr<AA[]> parr1(new AA[2]{string("西施1"),string("西施2")});  // 指定初始化
    	parr1[0].m_name = "西施1";
    	cout << "parr1[0].m_name= " << parr1[0].m_name << endl;
    	parr1[1].m_name = "西施2";
    	cout << "parr1[1].m_name= " << parr1[1].m_name << endl;
    
  • shared_ptr的线程安全性:

    shared_ptr的引用计数本身是线程安全(引用计数是原子操作)。

    多个线程同时读同一个shared_ptr对象是线程安全的。

    如果是多个线程对同一个shared_ptr对象进行读和写,则需要加锁。

    多线程读写shared_ptr所指向的同一个对象,不管是相同的shared_ptr对象,还是不同的shared_ptr对象,也需要加锁保护。

  • 如果unique_ptr能解决问题,就不要使用shared_ptr。unique_ptr的效率更高,占用的资源更少。

7. 智能指针的删除器

在默认情况下,在智能指针过期的时候,用delete 原始指针,释放它管理的资源。

程序员可以自定义删除器,改变智能指针释放资源的行为。

删除器可以是全局函数,仿函数和lambda表达式,形参是原始指针。

#include <iostream>
#include <memory>

class AA {
public:
	std::string m_name;
	AA() { std::cout << m_name << "调用析构函数AA()" << std::endl; }
	AA(const std::string& name) :m_name(name)
	{
		std::cout << "调用构造函数AA(" << m_name << ")" << std::endl;
	}
	~AA() { std::cout << "调用了析构函数~AA(" << m_name << ")" << std::endl; }
};

void deletefunc(AA* a)	// 删除器,普通函数
{
	std::cout << "自定义删除器(全局函数)" << std::endl;
	delete a;
}

class deleteclass {		// 删除器,仿函数
public:
	void operator()(AA* a)
	{
		std::cout << "自定义删除器(仿函数)" << std::endl;
		delete a;
	}
};

auto deletelamb = [](AA* a)		// 删除器,Lambda表达式
{
	std::cout << "自定义删除器(Lambda)" << std::endl;
	delete a;
};

//! 智能指针调用删除器的时候,会把资源的原始指针传进来。
//! 自定义删除器的目的是希望释放资源的时候可以做其他的事情


int main(void)
{
	//std::shared_ptr<AA> pa1 = std::make_shared<AA>("西施a");	// 用缺省的删除器

	//std::shared_ptr<AA> pa1(new AA("西施a"), deletefunc);	// 删除器,普通函数
	//std::shared_ptr<AA> pa2(new AA("西施b"), deleteclass());	// 删除器,仿函数(匿名对象)
	//std::shared_ptr<AA> pa3(new AA("西施c"),deletelamb);	// 删除器,Lambda表达式

	//std::unique_ptr<AA, decltype(deletefunc)*> pu1(new AA("西施1"), deletefunc);	// 删除器,普通函数
	//std::unique_ptr<AA, void (*)(AA*)> pu0(new AA("西施1"),deletefunc);	// 删除器,普通函数(第二个模板参数用的是函数指针)
	//std::unique_ptr<AA, deleteclass> pu2(new AA("西施2"),deleteclass()); // 删除器,仿函数(匿名对象)
	std::unique_ptr<AA, decltype(deletelamb)> pu3(new AA("西施3"), deletelamb);	// 删除器,Lambda表达式



}

8. weak_ptr智能指针

8.1 shared_ptr存在的问题

​ shared_ptr内部维护了一个共享的引用计数器,多个shared_ptr可以指向同一个资源。

​ 如果出现了循环引用,则引用计数器永远不归0,资源就不会释放。

#include <iostream>
#include <memory>

class BB;

class AA {
public:
	std::string m_name;
	AA() { std::cout << m_name << "调用析构函数AA()" << std::endl; }
	AA(const std::string& name) :m_name(name)
	{
		std::cout << "调用构造函数AA(" << m_name << ")" << std::endl;
	}
	~AA() { std::cout << "调用了析构函数~AA(" << m_name << ")" << std::endl; }
	std::shared_ptr<BB> m_p;
};

class BB {
public:
	std::string m_name;
	BB() { std::cout << m_name << "调用析构函数BB()" << std::endl; }
	BB(const std::string& name) :m_name(name)
	{
		std::cout << "调用构造函数BB(" << m_name << ")" << std::endl;
	}
	~BB() { std::cout << "调用了析构函数~BB(" << m_name << ")" << std::endl; }
	std::shared_ptr<AA> m_p;
};

int main(void)
{
	std::shared_ptr<AA> pa = std::make_shared<AA>("西施a");
	std::shared_ptr<BB> pb = std::make_shared<BB>("西施b");

	pa->m_p = pb;	
	pb->m_p = pa;
	//! 只调用了构造函数,没有调用析构函数
}

8.2 weak_ptr是什么

weak_ptr 是为了配合shared_ptr而引入的,它指向一个由 shared_ptr管理的资源但不影响资源的生命周期。也就是说,将一个weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数。

不管是否有 weak_ptr 指向,如果最后一个指向资源的shared_ptr被销毁,资源就会被释放。

weak_ptr 更像是 shared_ptr的助手而不是智能指针。

#include <iostream>
#include <memory>

class BB;

class AA {
public:
	std::string m_name;
	AA() { std::cout << m_name << "调用析构函数AA()" << std::endl; }
	AA(const std::string& name) :m_name(name)
	{
		std::cout << "调用构造函数AA(" << m_name << ")" << std::endl;
	}
	~AA() { std::cout << "调用了析构函数~AA(" << m_name << ")" << std::endl; }
	std::weak_ptr<BB> m_p;
};

class BB {
public:
	std::string m_name;
	BB() { std::cout << m_name << "调用析构函数BB()" << std::endl; }
	BB(const std::string& name) :m_name(name)
	{
		std::cout << "调用构造函数BB(" << m_name << ")" << std::endl;
	}
	~BB() { std::cout << "调用了析构函数~BB(" << m_name << ")" << std::endl; }
	std::weak_ptr<AA> m_p;
};

int main(void)
{
	std::shared_ptr<AA> pa = std::make_shared<AA>("西施a");
	std::shared_ptr<BB> pb = std::make_shared<BB>("西施b");

	std::cout << " pa.use_count()=" << pa.use_count() << std::endl;
	std::cout << " pb.use_count()=" << pb.use_count() << std::endl;

	pa->m_p = pb;	
	pb->m_p = pa;

	std::cout << " pa.use_count()=" << pa.use_count() << std::endl;
	std::cout << " pb.use_count()=" << pb.use_count() << std::endl;

}

8.3 weak_ptr的使用方法

weak_ptr没有重载 -> 和 * 操作符,不能直接访问资源。

​ 有以下成员函数:

  • 1)operator=();

    把shared_ptr或weak_ptr赋值给weak_ptr。

  • 2)expired();

    判断它指资源是否已过期(已经被销毁)。

  • 3)lock();

    返回shared_ptr,如果资源已过期,返回空的shared_ptr。

  • 4)reset();

    将当前weak_ptr指针置为空。

  • 5)swap(); // 交换。

#include <iostream>
#include <memory>

class BB;

class AA {
public:
	std::string m_name;
	AA() { std::cout << m_name << "调用析构函数AA()" << std::endl; }
	AA(const std::string& name) :m_name(name)
	{
		std::cout << "调用构造函数AA(" << m_name << ")" << std::endl;
	}
	~AA() { std::cout << "调用了析构函数~AA(" << m_name << ")" << std::endl; }
	std::weak_ptr<BB> m_p;
};

class BB {
public:
	std::string m_name;
	BB() { std::cout << m_name << "调用析构函数BB()" << std::endl; }
	BB(const std::string& name) :m_name(name)
	{
		std::cout << "调用构造函数BB(" << m_name << ")" << std::endl;
	}
	~BB() { std::cout << "调用了析构函数~BB(" << m_name << ")" << std::endl; }
	std::weak_ptr<AA> m_p;
};

int main(void)
{
	std::shared_ptr<AA> pa = std::make_shared<AA>("西施a");

	{
		std::shared_ptr<BB> pb = std::make_shared<BB>("西施b");

		// 在语句块内部,西施b的资源没有过期,
		pa->m_p = pb;	// 将weak_ptr与shared_ptr绑定
		pb->m_p = pa;

		std::shared_ptr<BB> pp = pa->m_p.lock();
		// lock()成员函数是线程安全的,是原子操作,如果提升成功了,就返回一个有效的shared_ptr,资源的引用计数加1;如果提升失败了,返回空的shared_ptr
		if (pp == nullptr) {
			std::cout << "语句块内部:pa->m_p已过期" << std::endl;
		}
		else {
			std::cout << "语句块内部:pa->m_p.lock()->m_name" << pa->m_p.lock()->m_name << std::endl;
		}
	}

	// 在语句块外部,西施b的资源已过期,
	std::shared_ptr<BB> pp = pa->m_p.lock();
	if (pp == nullptr) {
		std::cout << "语句块外部:pa->m_p已过期" << std::endl;
	}
	else {
		std::cout << "语句块外部:pa->m_p.lock()->m_name" << pa->m_p.lock()->m_name << std::endl;
	}

}
  • weak_ptr 不控制对象的生命周期,但是,它知道对象是否还活着。
  • 用lock()函数把它可以提升为shared_ptr,如果对象还活着,返回有效的shared_ptr;如果对象已经死了,提升会失败,返回一个空的shared_ptr。
  • 提升的行为 lock() 是线程安全的。

= std::make_shared(“西施b”);

	// 在语句块内部,西施b的资源没有过期,
	pa->m_p = pb;	// 将weak_ptr与shared_ptr绑定
	pb->m_p = pa;

	std::shared_ptr<BB> pp = pa->m_p.lock();
	// lock()成员函数是线程安全的,是原子操作,如果提升成功了,就返回一个有效的shared_ptr,资源的引用计数加1;如果提升失败了,返回空的shared_ptr
	if (pp == nullptr) {
		std::cout << "语句块内部:pa->m_p已过期" << std::endl;
	}
	else {
		std::cout << "语句块内部:pa->m_p.lock()->m_name" << pa->m_p.lock()->m_name << std::endl;
	}
}

// 在语句块外部,西施b的资源已过期,
std::shared_ptr<BB> pp = pa->m_p.lock();
if (pp == nullptr) {
	std::cout << "语句块外部:pa->m_p已过期" << std::endl;
}
else {
	std::cout << "语句块外部:pa->m_p.lock()->m_name" << pa->m_p.lock()->m_name << std::endl;
}

}


- **`weak_ptr` 不控制对象的生命周期,但是,它知道对象是否还活着。**
- **用lock()函数把它可以提升为shared_ptr,如果对象还活着,返回有效的shared_ptr;如果对象已经死了,提升会失败,返回一个空的shared_ptr。**
- **提升的行为 lock() 是线程安全的。**

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值