C++重学(8)——智能指针

智能指针

属于STL的智能指针,头文件<memory>

智能指针分类

  • unique_ptr 独享所有权(单个智能指针指向一块内存)
  • shared_ptr共享所有权(多个智能指针指向同一块内存)

unqiue_ptr

创建方式

语法 :

构建方法说明
std::unique_ptr<T> 智能指针变量名;创建空的智能指针
std::unique_ptr<T> 智能指针变量名(new T);创建类型为T的智能指针
std::unique_ptr<T> 智能指针变量名= std::make_unique<T>();创建类型为T的智能指针(C++14)
通过unique_ptr<T>::move()转移获得转移方法详情
#include <iostream>
#include <memory>

class Student
{
public:
	Student(int ID) :m_ID(ID) {
		std::cout << "学号" << this->m_ID << "被构造" << std::endl;
	}
	~Student() {
		std::cout << "学号" << this->m_ID << "被销毁" << std::endl;
	};

private:
	int m_ID;
};

void func1() {
	std::unique_ptr<Student> p1(new Student(7));
	std::unique_ptr<Student> p2 = std::make_unique<Student>(8);
}

int main()
{
	func1();
	std::cout << "进入主函数" << std::endl;
	return 0;
}

执行结果如下图所示:
在这里插入图片描述

使用智能指针之后,就可以不用关心delete的时机,这些都由智能指针帮忙释放。

常用方法

方法说明范例
方法1:ptr1 == nullptr
方法2:!ptr1
是否为空范例1
std::reset()重置:会delete所管理的指针范例2
std::release()释放:返回被管理的指针,智能指针置空范例3
std::move(智能指针)转移被管理的指针给其他unique_ptr,本身置空范例4
std::get()获取被管理的指针,智能指针仍在管理范例5
范例1:是否为空
//判断智能指针是否为空
void func2() {
	std::unique_ptr<int> ptr;
	// 方法1
	if (ptr == nullptr) {
		std::cout << "方法1中 智能指针为空" << std::endl;
	}
	// 方法2
	if (!ptr) {
		std::cout << "方法2中 智能指针为空" << std::endl;
	}
}

在这里插入图片描述

返回方法汇总表

范例2:重置智能指针
//重置智能指针
void func3() {
	std::unique_ptr<int> ptr(new int(10));
	ptr.reset();
	if (ptr == nullptr) {
		std::cout << "智能指针为空" << std::endl;
	}
}

在这里插入图片描述

返回方法汇总表

范例3:释放智能指针
//释放智能指针
void func4() {
	std::unique_ptr<int> ptr(new int(10));
	//std::unique_ptr<int> ptr2 = ptr.release();    //会报错
	int* p = ptr.release();
	std::cout << "p指针的值为 " << *p << std::endl;
	if (ptr == nullptr) {
		std::cout << "释放后智能指针为空" << std::endl;
	}
}

在这里插入图片描述

注意:不能使用智能指针来接受智能指针的释放

返回方法汇总表

范例4:转移智能指针的所有权

该方法就是可以解决范例3中的报错情况。

//转移智能指针
void  func5() {
	std::unique_ptr<int> ptr(new int(10));
	std::unique_ptr<int> ptr2 = std::move(ptr);
	if (ptr == nullptr) {
		std::cout << "转移后的智能指针为空" << std::endl;
	}
	std::cout << "新的指针的值为 " << *ptr2 << std::endl;
}

在这里插入图片描述

返回方法汇总表

范例5:获取智能指针管理的指针
//获取被管理的指针
void func6() {
	std::unique_ptr<int> ptr(new int(10));
	//std::unique_ptr<int> ptr2 = ptr.get();	//会报错
	int* p = ptr.get();
	std::cout << "p指针的值为 " << *p << std::endl;
	if (ptr == nullptr) {
		std::cout << "获取后的智能指针为空" << std::endl;
	}
	else {
		std::cout << "获取后的智能指针不为空,仍然有管理,值为" << *ptr <<std::endl;
	}
}

在这里插入图片描述

注意:不能使用智能指针来接受智能指针的获取(智能指针在初始化时不能用其他智能指针的get()来初始化)

返回方法汇总表

类中包含智能指针的情况

class TESTPTR_B {
public:
	TESTPTR_B() { std::cout << "==========TESTPTR_B 被创造==========" << std::endl; }
	~TESTPTR_B() { std::cout << "==========TESTPTR_B 被析构==========" << std::endl; }

};

class TESTPTR
{
public:
	TESTPTR() :iprt(new TESTPTR_B) { std::cout <<  "TESTPTR 被创造" << std::endl; }
	~TESTPTR() { std::cout << "TESTPTR 被析构" << std::endl;}

private:
	std::unique_ptr<TESTPTR_B> iprt;
};


int main()
{
	TESTPTR a;
	return 0;
}

在这里插入图片描述

不难看出,当TESTPTR对象a被exe回收的时候,也会把TESTPTR_B的对象析构,防止内存泄露。

注意:智能指针unqiue_ptr中途中被get()后,被管理指针(iprt)在其他地方被delete,那么在unqiue_ptr就需要release被管理的指针(iprt),不然unqiue_ptr在被回收时会再次delete 被管理的指针(iprt)从而造成程序崩溃。

class TESTPTR_B {
public:
	TESTPTR_B() { std::cout << "==========TESTPTR_B 被创造==========" << std::endl; }
	~TESTPTR_B() { std::cout << "==========TESTPTR_B 被析构==========" << std::endl; }

};

class TESTPTR
{
public:
	TESTPTR() :iprt(new TESTPTR_B) { std::cout <<  "TESTPTR 被创造" << std::endl; }
	~TESTPTR() { std::cout << "TESTPTR 被析构" << std::endl;}
	void func8() {
		TESTPTR_B *tmp = iprt.get();
		delete tmp;
		iprt.release();		//因为tmp被delete,不加上这句代码,程序将会崩溃,因为iprt会再次释放内存
	}

private:
	std::unique_ptr<TESTPTR_B> iprt;
};


int main()
{
	TESTPTR a;
	a.func8();
	std::cout << "main函数结束" << std::endl;
	return 0;
}

在这里插入图片描述

unqiue_ptr总结以及注意点

  • std::unique_ptr<T> Ptr = std::make_unique<T>(参数……)该方法在C++14以后才被使用(含C++14)

  • 当指针交给智能指针管理之后,如果想要delete,就调用unqiue_ptr的reset就好

  • unqiue_ptr不能通过拷贝构造生成,也不可用=运算符来赋值,只能通过移动来赋值。例子如下

在这里插入图片描述

shared_ptr

结构概述:

有两块数据组成,一个是数据块(存放指针),一个是计数块(记录引用计数)

创建方式

方法说明
std::shared_ptr<T> 智能指针变量名;创建空的智能指针
std::shared_ptr<T> 智能指针变量名(new T);创建类型为T的智能指针
std::shared_ptr<T> 智能指针变量名= std::make_shared<T>();创建类型为T的智能指针(C++14)
std::shared_ptr<T> prt2(prt1)prt2的指向 prt1所指向的内容

在这里插入图片描述

void func10() {
	std::shared_ptr<int> p1(new int(10));
	std::shared_ptr<int> p2 = std::make_shared<int>(20);
	std::shared_ptr<int> p3(p1);
	std::shared_ptr<int> p4;
	std::cout << "智能指针 p1 的值为" << *p1 << std::endl;
	std::cout << "智能指针 p2 的值为" << *p2 << std::endl;
	std::cout << "智能指针 p3 的值为" << *p3 << std::endl;
	if (p4 != nullptr) {
		std::cout << "智能指针 p4 的值为" << *p4 << std::endl;
	}
	else {
		std::cout << "智能指针 p4 的值为空" << std::endl;
	}
}

常用方法

方法说明范例
方法1:ptr1 == nullptr
方法2:!ptr1
是否为空
use_count()返回被管理的指针的引用计数范例1
智能指针=nullptr智能指针置空范例2
reset()智能指针重置范例3
reset(new <T>)智能指针重置后,重新管理新的指针范例3
范例1:shared_ptr指针的引用计数

在这里插入图片描述

void func11() {
	std::shared_ptr<int> p1;
	std::cout << "空智能指针计数为 "<<p1.use_count() << std::endl << std::endl;
	std::shared_ptr<int> p2(new int(10));
	std::shared_ptr<int> p3(p2);
	std::cout << "p2智能指针计数为 " << p2.use_count() << std::endl;
	std::cout << "p3智能指针计数为 " << p3.use_count() << std::endl;
}
范例2:shared_ptr指针的置空

在这里插入图片描述

void func12() {
	std::shared_ptr<int> p1(new int(10));
	std::shared_ptr<int> p2(p1);
	std::cout << "p1智能指针计数为 " << p1.use_count() << std::endl;

	p2 = nullptr;
	std::cout << "p2智能指针置空后,p1智能指针计数为 " << p1.use_count() << std::endl;

	if (p2 == nullptr) {
		std::cout << "此时智能指针 p2 的值为空" << std::endl;
	}
}
范例3:智能指针重置后,重新管理新的指针

在这里插入图片描述

void func13() {
	std::shared_ptr<int> p1(new int(10));
	std::shared_ptr<int> p2(p1);
	std::shared_ptr<int> p3(p1);
	std::shared_ptr<int> p4(p1);
	std::cout << "p1智能指针计数为 " << p1.use_count() << std::endl << std::endl;

	p2.reset();
	std::cout << "p2智能指针重置后p2计数为 " << p2.use_count() << std::endl;
	std::cout << "此时p1智能指针计数为 " << p1.use_count() << std::endl << std::endl;

	p3.reset(new int(20));
	std::cout << "p3智能指针重置后,重新管理新的指针,此时p3计数为 " << p3.use_count() << std::endl;
	std::cout << "此时p1智能指针计数为 " << p1.use_count() << std::endl << std::endl;
}

自定义删除器

智能指针在计数变为0的时候会调用析构函数的delete。但是管理数组指针的时候就有局限性,就需要使用自定义删除器。

主要有两种:

  • 回调函数 (参数为智能指针管理的数据类型)

  • 函数对象(仿函数)

  • lambda表达式 (lambda表达式其实就是上面的中的另外一种写法罢了)

智能指针管理指针数组时

在这里插入图片描述

class MyClass
{
public:
	MyClass() { std::cout << " 构造对象 " << std::endl; }
	~MyClass() { std::cout << " 析构对象 " << std::endl; }
};

void self_delete(MyClass * ptr) {
    //注意参数类型需要是指针类型
	delete[] ptr;
	std::cout << " 删除数组指针 " << std::endl;
}

void func14() {
	std::shared_ptr<MyClass[]> p1(new MyClass[5](), self_delete);
}

智能指针管理 含有指针的 STL容器

一般情况下,开发过程中如果要用到数组一般是使用STL的vector容器来代替数组,也可以交给智能指针管理。这里我用的是自定义数据类型。
在这里插入图片描述

void self_class_delete(std::vector<MyClass *> * ptr) {
   	//注意参数类型需要是指针类型
	std::cout << std::endl;
	static int i = 0;
	for (std::vector<MyClass *>::iterator it = ptr->begin(); it != ptr->end(); it++) {
		std::cout << std::endl << "正在删除第"<< ++i << "个元素  *it的数据类型为 " << typeid(*it).name() << std::endl;
		delete *it;
		*it = nullptr;
		if (it == ptr->end()) {
			std::cout << " 到尾了 " << std::endl;
		}
	}
	std::cout << " 删除完成 " << std::endl;
}

void func15() {
	std::vector<MyClass *> * MyClassPtrVct = new std::vector<MyClass *>;
	MyClassPtrVct->reserve(5);	//预留5个空间,防止多次扩展
	MyClass *p1 = new MyClass;
	MyClass *p2 = new MyClass;
	MyClass *p3 = new MyClass;
	std::cout << std::endl << " 指针加入vector容器 " << std::endl;
	MyClassPtrVct->push_back(p1);
	MyClassPtrVct->push_back(p2);
	MyClassPtrVct->push_back(p3);

	std::cout << std::endl << " vector容器交给shared_ptr智能指针管理 " << std::endl;

	std::shared_ptr<std::vector<MyClass *>> AiPtr(MyClassPtrVct, self_class_delete);
	AiPtr.reset();
}
使用函数对象作为自定义删除器

函数对象:自定义类型对()重载,也就是仿函数。注意仿函数和自定义的类要分开写,详情见下面遇到的问题。解决方法(正确写法)点此跳转

使用中遇到的问题:

主要遇到两种情况

  • 智能指针存放自定义数据类型数组
  • 智能指针存放自定义数据类型

问题描述:这两种情况使用自定义删除器,在创建智能指针的时候会额外构造对象!!虽然能正常释放……

eg1:智能指针中存放数组,数组存放自定义数据类型,使用函数对象作为自定义删除器来回收内存。

下面是错误写法

class DeleteTest {
public:
	DeleteTest() {
		static int i = 0;
		this->m_ID = ++i;
		std::cout << " DeleteTest " << this->m_ID << " 构造 " << std::endl;
	}
	~DeleteTest() {
		std::cout << " DeleteTest " << this->m_ID << " 析构 " << std::endl;
	}
    //下面的重载()就是问题所在
	void operator()(DeleteTest * ptr) {
		std::cout << " DeleteTest 数组被删除 " << std::endl;
		// delete ptr;  //这句会直接报错
		delete[] ptr;
	}
private:
	int m_ID;
};

void func16() {

	std::shared_ptr<DeleteTest> p(new DeleteTest[3], DeleteTest());
	std::cout << "创建结束" << std::endl;
	p.reset();
	std::cout << "释放智能指针所有权结束" << std::endl;
}

下图中红色方框都是问题所在

在这里插入图片描述

问题分析及解决方法

上述问题中主要的错误在于自定义数据类型和仿函数(函数对象)没有分开写,导致重载的()污染了原来的代码逻辑。
在这里插入图片描述

class Student {
public:
	Student(){ 
		static int  i = 0;
		this->m_ID = ++i;
		std::cout <<"学生  "<< this->m_ID <<"被构造" << std::endl;
	}
	~Student() {
		std::cout << "学生  " << this->m_ID << "被析构" << std::endl;
	}
private:
	int m_ID;
};

class CorrectDeleter{
public:
	void operator()(Student* ptr) {
		std::cout << " Student 数组被删除 " << std::endl;
		delete[] ptr;
	}
};

void func16_2() {
	std::shared_ptr<Student> p(new Student[3], CorrectDeleter());
	std::cout << "创建结束" << std::endl;
	p.reset();
	std::cout << "释放智能指针所有权结束" << std::endl;
}
使用C++11 lambda表达式作为删除器

lambda:lambda是C++11的新特性,可以代替 函数或者函数对象

在这里插入图片描述

class DeleteArrFunc
{
public:
	DeleteArrFunc(int i):m_ID(i){

		std::cout  <<" DeleteArrFunc "<< this->m_ID <<" 被构造"<< std::endl;
	}
	~DeleteArrFunc() {
		std::cout  << " DeleteArrFunc " << this->m_ID <<" 析构 "  << std::endl;
	}
	int m_ID;
};


void func17() {
	//{1,2,3}用的是C++11新特性中的初始化方法
	std::shared_ptr<DeleteArrFunc> p(new DeleteArrFunc[3]{ 1,2,3 }, [](DeleteArrFunc * ptr) {
		std::cout << " DeleteArrFunc 数组被删除 " << std::endl;
		delete[] ptr;
	});
	std::cout << "结束" << std::endl;
	p.reset();
	std::cout << "释放智能指针所有权结束" << std::endl;
}

易错点:

1、不能使用栈中之中来构造智能指针

应为栈中的数据在超出作用域之后就会被程序回收,然后智能指针也会对这块数据

void func18()
{
   int x = 12;
   std::shared_ptr<int> ptr(&x);	
}

需修改为

void func19()
{
   int *x = new int(12);
   std::shared_ptr<int> ptr(x);	
}
2、不能用一个原始堆区数据来构造
    int *num = new int(23);
    std::shared_ptr<int> p1(num);
    
    std::shared_ptr<int> p2(p1);  // 正确使用方法
    std::shared_ptr<int> p3(num); // 不推荐

    std::cout << "p1 Reference = " << p1.use_count() << std::endl; // 输出 2
    std::cout << "p2 Reference = " << p2.use_count() << std::endl; // 输出 2
    std::cout << "p3 Reference = " << p3.use_count() << std::endl; // 输出 1

weak_ptr

概述

作用:不会增加shared_ptr所管理指针的引用计数,作为shared_ptr的辅助指针。

用途:解决shared_ptr的环形问题

创建方法

只能通过shared_ptr来赋值创造

shared_ptr<A> ptr_A(new A);
weak_ptr<A> test = ptr_A;	//正确
weak_ptr<A> test(ptr_A);	//正确
weak_ptr<A> test(new A);	//报错

常用方法

方法说明
lock()获取shared_ptr对象
expired()检测是否失效,注意判断的是 是否失效
swap()用于交换相同类型的
void func28() {
	class CA
	{
	public:
		CA(int num) :ID(num) { cout << "CA " << ID << " 被创造" << endl; }
		~CA() { cout << "CA " << ID << " 被析构" << endl; }
		void show() { cout << "I'm CA ,ID = " << ID << endl; }
		int ID;
	};
	class CB
	{
	public:
		CB() { cout << "CB 被创造" << endl; }
		~CB() { cout << "CB 被析构" << endl; }
		void show() { cout << "I'm CB" << endl; }
	};

	shared_ptr<CA> objA(new CA(1));
	shared_ptr<CA> objA_2(new CA(2));
	shared_ptr<CB> objB(new CB);
	weak_ptr<CA> testCA(objA);
	weak_ptr<CA> testCA_2(objA_2);
	weak_ptr<CB> testCB(objB);

	//lock用法
	testCA.lock()->show();		//I'm CA ,ID = 1

	//swap交换 
	cout << "交换后" << endl;
	testCA.swap(testCA_2);		//成功
	//testCA.swap(testCB);		//报错,因为所管理的类型不同
	testCA.lock()->show();		//I'm CA ,ID = 2
	testCA_2.lock()->show();	//I'm CA ,ID = 1

	// expired用法
	if (!testCA.expired()) {
		cout << "此时  testCA 仍然有效" << endl;
	}
	testCA.reset();
	if (testCA.expired()) {
		cout << "此时  testCA 失效" << endl;
	}
}

在这里插入图片描述

shared_ptr的环形问题

在这里插入图片描述

class B;
class A
{
public:
	A() { cout << "A 被创造" << endl; }
	~A() { cout << "A 被销毁" << endl; }
	void show() { cout << "我是 A  " << endl; }
	shared_ptr<B> CA_Manage_B_ptr;		//A类中管理A的智能指针
};
class B
{
public:
	B() { cout << "B 被创造" << endl; }
	~B() { cout << "B 被销毁" << endl; }
	void show() { cout << "我是 B " << endl; }
	shared_ptr<A> CB_Manage_A_ptr;		//B类中管理A的智能指针

};

void func27() {
	//创建两个智能指针来管理A、B对象
	shared_ptr<A> objA(new A);
	shared_ptr<B> objB(new B);

	cout<< objA.use_count() << endl;	//输出1
	cout<< objB.use_count() << endl;	//输出1

	objA->CA_Manage_B_ptr = objB;
	objB->CB_Manage_A_ptr = objA;

	cout << objA.use_count() << endl;	//输出2
	cout << objB.use_count() << endl;	//输出2
}

int main(void)
{
	func27();
	return 0;
}

执行过后发现没有对象被析构

在这里插入图片描述

解决方法

将任意一个类中的shared_ptr改写为weak_ptr即可。下面将CB_Manage_A_ptr的类型从shared_ptr改成weak_ptr

class B;
class A
{
public:
	A() { cout << "A 被创造" << endl; }
	~A() { cout << "A 被销毁" << endl; }
	void show() { cout << "我是 A  " << endl; }
	shared_ptr<B> CA_Manage_B_ptr;		//A类中管理A的智能指针
};
class B
{
public:
	B() { cout << "B 被创造" << endl; }
	~B() { cout << "B 被销毁" << endl; }
	void show() { cout << "我是 B " << endl; }
	weak_ptr<A> CB_Manage_A_ptr;		//此处改为weak_ptr
};
void func27() {
	//创建两个智能指针来管理A、B对象
	shared_ptr<A> objA(new A);
	shared_ptr<B> objB(new B);

	cout<< objA.use_count() << endl;	//输出1
	cout<< objB.use_count() << endl;	//输出1

	objA->CA_Manage_B_ptr = objB;
	objB->CB_Manage_A_ptr = objA;

	cout << objA.use_count() << endl;	//输出1,因为weak_ptr不会增加使用计数
	cout << objB.use_count() << endl;	//输出2
}

在这里插入图片描述

参考文献

C++ 智能指针 shared_ptr 详解与示例 http://t.csdn.cn/jQGRL

C++ 智能指针 unique_ptr 详解与示例 http://t.csdn.cn/EuR0U

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值