[C++11] 智能指针

这篇具有很好参考价值的文章主要介绍了[C++11] 智能指针。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

         长路漫漫,唯剑作伴。        

目录

         长路漫漫,唯剑作伴。        

为什么需要智能指针

RAII

使用RAII思想管理内存

重载 * 和->

总结一下智能指针的原理:

C++的智能指针和拷贝问题

auto_ptr        (C++98)   ​编辑

auto_ptr的实现原理:管理权转移的思想,

unique_ptr        (C++11)

std::shared_ptr        (C++11)

        sheared_ptr 的简化实现

        关于循环引用的问题(Circular reference)

weak_ptr        打破循环引用(C++11)

        weak_ptr 的简化实现

定制删除器

定制删除器的使用

unique_ptr 定制删除器的模拟实现

相关链接 :


为什么需要智能指针

int div()
{
	int a, b;
	cin >> a >> b;
	if (b == 0)
		throw invalid_argument("除0错误");

	return a / b;
}

void func()
{
	int* p1 = new int[10]; // 这里亦可能会抛异常

	int* p2 = new int[10]; // 这里亦可能会抛异常
	int* p3 = new int[10]; // 这里亦可能会抛异常
	int* p4 = new int[10]; // 这里亦可能会抛异常

	try
	{
		div();
	}
	catch (...)
	{
		delete[] p1;
		delete[] p2;
		delete[] p3;
		delete[] p4;

		throw;
	}

	delete[] p1;
	delete[] p2;
	delete[] p3;
	delete[] p4;
}

int main()
{
	try
	{
		func();
	}
	catch (const exception& e)
	{
		cout << e.what() << endl;
		// ...
	}

	return 0;

        可以发现上面这段代码是典型的异常处理中可能会碰到内存泄漏或析构异常的例子,我们当然可以为每个new[]都尝试着抛出异常,但那样太麻烦了,所以我们引入了智能指针

RAII

RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内 存、文件句柄、网络连接、互斥量等等)的简单技术。

在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在 对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。这种做 法有两大好处:

  • 不需要显式地释放资源。
  • 采用这种方式,对象所需的资源在其生命期内始终保持有效。

使用RAII思想管理内存

// 使用RAII思想设计的SmartPtr类

template<class T>
class SmartPtr{
public:
    SmartPtr(T* ptr = nullptr)
        : _ptr(ptr){
        cout << "get " << _ptr << endl;

    }
    ~SmartPtr(){
        if (_ptr)
            cout << "delet " <<_ptr<< endl;
            delete _ptr;
    }

private:
    T* _ptr;
};

void Func(){
    int* pa = new int(10);
    cout << " pa " << pa << endl;
    SmartPtr<int> sp1(pa);

}
int main(){
    Func();
    return 0;
}

[C++11] 智能指针

重载 * 和->

        上述的SmartPtr还不能将其称为智能指针,因为它还不具有指针的行为。指针可以解引用,也可 以通过->去访问所指空间中的内容,因此:SmartPtr模板类中还得需要将* 、->重载下,才可让其像指针一样去使用。

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

//使用RAII 思想管理内存


template<class T>
class SmartPtr{
public:
    SmartPtr(T* ptr = nullptr)
        : _ptr(ptr){
        //cout << "get " << _ptr << endl;

    }
    ~SmartPtr(){
        if (_ptr)
            //cout << "delet " <<_ptr<< endl;
            delete _ptr;
    }

    T& operator*(){ return *_ptr; }
    T* operator->(){ return _ptr; }

private:
    T* _ptr;
};

struct Date{
    int _year;
    int _month;
    int _day;
};

int main(){
    SmartPtr<int> sp1(new int);
    *sp1 = 10;
    cout << *sp1 << endl;
    SmartPtr<Date> spDate(new Date);
    // 需要注意的是这里应该是spDate.operator->()->_year = 2018;
    // 本来应该是spDate->->_year这里语法上为了可读性,省略了一个->
    spDate->_year = 2060;
    spDate->_month = 6;
    spDate->_day = 14;

    cout << spDate->_year << "/" << spDate->_month << "/" << spDate->_day << endl;
    return 0;
}

[C++11] 智能指针

        但是尽管如此,我们仍旧面临拷贝问题和析构问题,这里先抛出问题,文章后面会解释C++智能指针是如何解决这类的问题的,一步一步来,我们会解决拷贝问题,然后再解决析构问题(定制删除器

void copy_issue(){
    SmartPtr<int> sp1(new int);
    SmartPtr<int> sp2 = sp1;        //这里会析构2次而导致程序异常终止
}


void destructor_issue(){
    SmartPtr<int> sp1(new int[10]);  //这里的析构存在类型不匹配,没办法正确的调用 delete[],
                                    //类似的还有文件描述符,也没办法正确 close()...
}

总结一下智能指针的原理:

  • RAII特性
  • 重载operator*和opertaor->,具有像指针一样的行为
  • 处理拷贝问题
  • 定制删除器

C++的智能指针和拷贝问题

auto_ptr        (C++98)   

auto_ptr的实现原理:管理权转移的思想,

        下面简化模拟实现了一份skate::auto_ptr来了解它的原理:

namespace skate{
	template<class T>
	class auto_ptr{
	public:
		auto_ptr(T* ptr)
			:_ptr(ptr){
		}

		auto_ptr(auto_ptr<T>& sp)   //拷贝构造
			:_ptr(sp._ptr){
			// 管理权转移
			sp._ptr = nullptr;
		}

		auto_ptr<T>& operator=(auto_ptr<T>& ap){ //赋值重载
			// 检测是否为自己给自己赋值
			if (this != &ap){
				// 释放当前对象中资源
				if (_ptr)
					delete _ptr;

				// 转移ap中资源到当前对象中
				_ptr = ap._ptr;
				ap._ptr = NULL;
			}

			return *this;
		}

		~auto_ptr(){
			if (_ptr){
				cout << "delete:" << _ptr << endl;
				delete _ptr;
			}
		}

		// 像指针一样使用
		T& operator*(){
			return *_ptr;
		}

		T* operator->(){
			return _ptr;
		}
	private:
		T* _ptr;
	};
}

//结论:auto_ptr是一个失败设计,很多公司明确要求不能使用auto_ptr
int main(){
	skate::auto_ptr<int> sp1(new int);
	*sp1 = 10;

	skate::auto_ptr<int> sp2(sp1); // 管理权转移

	   //sp1悬空
	*sp1 = 20;			//空指针异常
	*sp2 = 30;
	cout << *sp2 << endl;
	cout << *sp1 << endl;
	return 0;
}

unique_ptr        (C++11)

C++11中开始提供更靠谱的unique_ptr 

[C++11] 智能指针
unique_ptr的实现原理:简单粗暴的防拷贝,下面简化模拟实现了一份UniquePtr来了解它的原理

// C++11库才更新智能指针实现
// C++11出来之前,boost搞除了更好用的scoped_ptr/shared_ptr/weak_ptr
// C++11将boost库中智能指针精华部分吸收了过来
// C++11->unique_ptr/shared_ptr/weak_ptr

// unique_ptr/scoped_ptr
// 原理:简单粗暴 -- 防拷贝
namespace skate{
	template<class T>
	class unique_ptr{
	public:
		unique_ptr(T* ptr)
			:_ptr(ptr){
		}

		~unique_ptr(){
			if (_ptr){
				cout << "delete:" << _ptr << endl;
				delete _ptr;
			}
		}

		// 像指针一样使用
		T& operator*(){
			return *_ptr;
		}

		T* operator->(){
			return _ptr;
		}

		unique_ptr(const unique_ptr<T>& sp) = delete;
		unique_ptr<T>& operator=(const unique_ptr<T>& sp) = delete;

	private:
		T* _ptr;
	};
}

std::shared_ptr        (C++11)

C++11中开始提供更靠谱的并且支持拷贝shared_ptr 
shared_ptr的原理:是通过引用计数的方式来实现多个shared_ptr对象之间共享资源。

  1. shared_ptr在其内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共享。
  2. 对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减一。
  3. 如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源
  4.  如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对象就成野指针了。

class MyClass{
public:
	MyClass(){
		std::cout << "MyClass constructor called" << std::endl;
	}

	~MyClass(){
		std::cout << "MyClass destructor called" << std::endl;
	}
};

int main(){

	std::shared_ptr<MyClass> ptr1(new MyClass);  //调用构造函数
	std::cout << ptr1.get() << " _ptr-> ptr1.use_count() = " << ptr1.use_count() << std::endl;
	std::shared_ptr<MyClass> ptr2 = ptr1;//调用赋值重载    计数++
	std::cout << ptr2.get() << " _ptr-> ptr2.use_count() = " << ptr2.use_count() << std::endl;
	std::shared_ptr<MyClass> ptr3(ptr1);//调用拷贝构造  计数++
	std::cout << ptr3.get() << " _ptr-> ptr3.use_count() = " << ptr3.use_count() << std::endl;

	ptr1.reset(); //计数--

	ptr2.reset();//计数--

	ptr3.reset();//计数为0  调用析构


	std::cout << ptr3.get() << " _ptr-> ptr3.use_count() = " << ptr3.use_count() << std::endl;

	return 0;
}

[C++11] 智能指针

 

[C++11] 智能指针

sheared_ptr 的简化实现

	template<class T>
	class shared_ptr
	{
	public:
		void Release()// 释放指向的对象和引用计数
		{
			if (--(*_pCount) == 0 && _ptr) //计数为0进行析构
			{
				cout << "delete" << _ptr << endl;
				delete _ptr;
				_ptr = nullptr;

				delete _pCount;
				_pCount = nullptr;
			}
		}

		// RAII思想
		shared_ptr(T* ptr)
			:_ptr(ptr)
			, _pCount(new int(1))
		{}

		~shared_ptr()
		{
			Release();//析构先时候 --计数,后确认析构
		}

		shared_ptr(const shared_ptr<T>& sp)
			:_ptr(sp._ptr)
			, _pCount(sp._pCount)
		{
			(*_pCount)++;
		}

		// sp1 = sp3
		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			//if (this != &sp)
			if (_ptr != sp._ptr)
			{
				Release();//先 --计数,后确认析构

				_ptr = sp._ptr;
				_pCount = sp._pCount;
				++(*_pCount);
			}

			return *this;
		}


		// 像指针一样
		T& operator*()
		{
			return *_ptr;
		}

		T* operator->()
		{
			return _ptr;
		}

		T* get() const
		{
			return _ptr;
		}

		int use_count()
		{
			return *_pCount;
		}

	private:
		T* _ptr;
		int* _pCount;
	};

        需要注意的是,这只是一份简单的shared_ptr模拟实现,并没有考虑线程安全等方面的问题。实际上,C++11中的标准库中的shared_ptr实现更加复杂和完善,具有线程安全等特性。

关于循环引用的问题(Circular reference)

        循环引用是指两个或多个智能指针对象直接或间接地引用彼此,形成一个引用链,导致资源无法正确释放。当涉及的对象之间的引用计数永远不会降为0时,就会发生循环引用问题。

        这种情况下,我们可以使用weak_ptr来打破循环引用,从而避免内存泄漏的问题。

[C++11] 智能指针

 循环引用分析: 

  1.  node1和node2两个智能指针对象指向两个节点,引用计数变成1,我们不需要手动delete。
  2. node1的_next指向node2,node2的_prev指向node1,引用计数变成2。
  3. node1和node2析构,引用计数减到1,但是_next还指向下一个节点。但是_prev还指向上一个节点。
  4. 也就是说_next析构了,node2就释放了。
  5. 也就是说_prev析构了,node1就释放了。
  6. 但是_next属于node的成员,node1释放了,_next才会析构,而node1由_prev管理,_prev属于node2成员,所以这就叫循环引用,谁也不会释放。

[C++11] 智能指针

weak_ptr        打破循环引用(C++11)

[C++11] 智能指针

weak_ptr 的简化实现

// 不直接参与指向资源的释放管理
	template<class T>
	class weak_ptr
	{
	public:
		weak_ptr()
			:_ptr(nullptr)
		{}

		weak_ptr(const shared_ptr<T>& sp)
			:_ptr(sp.get())
		{}

		weak_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			if (_ptr != sp.get())
			{
				_ptr = sp.get();
			}

			return *this;
		}

		// 像指针一样
		T& operator*()
		{
			return *_ptr;
		}

		T* operator->()
		{
			return _ptr;
		}

	public:
		T* _ptr;
	};
}

定制删除器

定制删除器的使用

class Date{
public:
	~Date(){
		cout << "~Date()" << endl;
	}
private:
	int _year = 1;
	int _month = 1;
	int _day = 1;
};

// unique_ptr/shared_ptr  默认释放资源用的delete
// 如何匹配申请方式去对应释放呢?

template<class T>
struct DeleteArray{
	void operator()(T* ptr){
		cout << "delete[]" << ptr << endl;
		delete[] ptr;
	}
};

template<class T>
struct Free{
	void operator()(T* ptr){
		cout << "free" << ptr << endl;
		free(ptr);
	}
};

struct Fclose{
	void operator()(FILE* ptr){
		cout << "fclose" << ptr << endl;
		fclose(ptr);
	}
};
void test_unique(){
	cout << "\ntest_unique()\n";
	// unique_ptr传类型
	std::unique_ptr<Date> up1(new Date);
	std::unique_ptr<Date, DeleteArray<Date>> up2(new Date[5]);
	std::unique_ptr<Date, Free<Date>> up3((Date*)malloc(sizeof(Date) * 5));
	std::unique_ptr<FILE, Fclose> up4((FILE*)fopen("Test.cpp", "r"));
}
void test_shared(){

	cout << "\ntest_shared()\n";
	// shared_ptr传对象
	std::shared_ptr<Date> sp1(new Date[5], DeleteArray<Date>());
	std::shared_ptr<Date> sp2(new Date[5], [](Date* ptr){cout << "lambda delete[] " << ptr << endl;; delete[] ptr; });
}
//定制删除器

int main(){
	test_unique();
	test_shared();
	return 0;
}

unique_ptr 定制删除器的模拟实现

[C++11] 智能指针

ps:

[C++11] 智能指针

C++11中的标准库中的shared_ptr实现更加复杂和完善,具有线程安全等特性。封装的也更加彻底,这里就不写模拟实现了

相关链接 :

https://cplusplus.com/reference/memory/auto_ptr/

https://cplusplus.com/reference/memory/unique_ptr/

https://cplusplus.com/reference/memory/shared_ptr/

https://cplusplus.com/reference/memory/weak_ptr/文章来源地址https://www.toymoban.com/news/detail-486224.html

到了这里,关于[C++11] 智能指针的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【C++11(三)】智能指针详解--RAII思想&循环引用问题

    💓博主CSDN主页:杭电码农-NEO💓   ⏩专栏分类:C++从入门到精通⏪   🚚代码仓库:NEO的学习日记🚚   🌹关注我🫵带你学习C++   🔝🔝 相信学C++的同学或多或少的听说过 智能指针这个词,博主刚听见这个词时 ,觉得它应该很复杂,并且很高大上,但不 管是多牛的东西,都是人写

    2024年02月04日
    浏览(46)
  • C++11补充:智能指针如std::unique_ptr如何添加自定义的deleter

    关于智能指针的问题,有时候为了方便,需要在析构时附加一些清理操作,或者,有的指针本身来自C的库文件中,这时候,就非常期望能够使用自定义的deleter, 但是标准C++创建函数如std::make_unique / std::make_shared 等不支持创建带有自定义deleter的智能指针,这时,我们只能使用

    2024年02月07日
    浏览(48)
  • C++11 新特性 ⑥ | 智能指针 unique_ptr、shared_ptr 和 weak_ptr

    目录 1、引言 2、unique_ptr 3、shared_ptr 4、weak_ptr 5、shared_ptr循环引用问题(面试题)

    2024年02月09日
    浏览(49)
  • C++11中的智能指针unique_ptr、shared_ptr和weak_ptr详解

    目录 1、引言 2、什么是智能指针? 3、在Visual Studio中查看智能指针的源码实现 4、独占式指针unique_ptr 4.1、查看unique_ptr的源码实现片段 4.2、为什么unique_ptr的拷贝构造函数和复制函数被delete了?(面试题) 4.3、使用unique_ptr独占式智能指针的实例 5、共享式指针shared_ptr  5.1、查

    2024年02月08日
    浏览(44)
  • 年轻不乏野心,想做年薪40万+的软件测试工程师?写给长途漫漫中的你...

    本人从事自动化测试10年多,之前在猪场工作,年薪突破40W+,算是一个生活过得去的码农。(仅代表本人) 目前从事自动化测试的薪资待遇还是很不错的,所以如果朋友们真的对自动化感兴趣的话可以坚持学下去,我也很乐意行业中出现更多能力出众的小伙伴们。 今天来给

    2023年04月27日
    浏览(53)
  • 【智能指针二】智能指针互转

    智能指针的转换和普通指针的转换是一样的,只是用到的转换函数中间多了一个pointer, 比如普通指针用到的static_cast、dynamic_cast、const_cast和reinterpret_cast。 智能指针用到的与之对应的转换函数为:static_pointer_cast、dynamic_pointer_cast、const_pointer_cast和reinterpret_pointer_cast。 如果你看

    2024年02月13日
    浏览(43)
  • C现代方法(第11章)笔记——指针

    —— 我记不清第十一条戒律是“你应该计算”还是“你不应该计算”了。 指针 是 C 语言最重要——也是最常被误解——的特性之一。由于指针的重要性,本书将用 3 章的篇幅对其进行讨论。本章侧重于基础知识,而第12章和第17章则介绍 指针的高级应用 。 本章将从 内存地

    2024年02月07日
    浏览(36)
  • 【链表OJ 11】复制带随机指针的链表

    前言:  💥🎈个人主页:​​​​​​Dream_Chaser~ 🎈💥 ✨✨刷题专栏:http://t.csdn.cn/UlvTc ⛳⛳本篇内容:力扣上链表OJ题目 目录 leetcode138. 复制带随机指针的链表 1. 问题描述 2.代码思路: 2.1拷贝节点插入到原节点的后面 2.2控制拷贝节点的random     2.3拷贝节点解下来尾插组成拷

    2024年02月09日
    浏览(51)
  • 【算法——双指针】LeetCode 11 盛最多水的容器

    题目描述: 解题思路:         如图所示:         1、我们考虑相距最远的两个柱子所能容纳水的面积。宽度是两根柱子之间的距离8;高度取决于两根柱子之间较短的那个,即左边柱子的高度3。水的面积就是3×8=24。         2、如果选择固定一根柱子,另外一根

    2024年02月12日
    浏览(33)
  • 写个简单的管理数组指针的智能指针

    模板智能数组指针 1.管理任意类型的数组指针 2.释放的时候自动删除数组指针指向的内存 3.通过指针计算数组中的个数   3.要有指针的样式和数组的样式 4.支持for范围查询和迭代器 5.实例用法  

    2024年02月08日
    浏览(51)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包