IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C++重学(8)——智能指针 -> 正文阅读

[C++知识库]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

参考文献

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

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

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-02-26 11:12:28  更:2022-02-26 11:12:40 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 7:05:55-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码