当前位置: 首页 > article >正文

C++——智能指针

目录

一、RAII

二、auto_ptr

三、unique_ptr

四、shared_ptr


一、RAII

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

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

二、auto_ptr

C++98版本的库中就提供了auto_ptr的智能指针,
auto_ptr的实现原理:管理权转移的思想

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

namespace SmartPtr
{
	template<class T>
	class auto_ptr
	{
	public:
		auto_ptr(T* ptr):ptr_(ptr)
		{}
		auto_ptr(auto_ptr<T>& ap) :ptr_(ap.ptr_)
		{
			ap.ptr_ = nullptr;// 所有权转移
		}
		auto_ptr<T>& operator=(auto_ptr<T>& ap)
		{
			// 检测是否为自己给自己赋值
			if (this != &ap)
			{
				// 释放当前对象中资源
				if (ptr_)
					delete ptr_;
				// 转移ap中资源到当前对象中
				ptr_ = ap.ptr_;
				ap.ptr_ = nullptr;
			}
			return *this;
		}
		~auto_ptr()
		{
			if (ptr_)
			{
				std::cout << "delete:" << ptr_ << std::endl;
				delete ptr_;
			}
		}
		// 像指针一样使用
		T& operator*()
		{
			return *ptr_;
		}
		T* operator->()
		{
			return ptr_;
		}
	private:
		T* ptr_;
	};
};

并且可以使用以下代码进行测试:

int main()
{
	using namespace std;
	// 测试基本功能
	int* p = new int(10);
	SmartPtr::auto_ptr<int> sp1(p);
	cout << "*sp1 = " << *sp1 << endl; // 测试 operator*

	// 测试所有权转移
	SmartPtr::auto_ptr<int> sp2(sp1);
	if (sp1.operator->() == nullptr)
		cout << "sp1 ownership transferred to sp2" << endl;

	cout << "*sp2 = " << *sp2 << endl;

	// 测试赋值操作符
	int* p2 = new int(20);
	SmartPtr::auto_ptr<int> sp3(p2);
	sp2 = sp3;

	if (sp3.operator->() == nullptr)
		cout << "sp3 ownership transferred to sp2" << endl;

	cout << "*sp2 after assignment = " << *sp2 << endl;

	return 0;
}

学习 auto_ptr 只是为了更了解智能指针的历史与后面的智能指针的改进, auto_ptr 基本算一个被时代遗弃的产物,所以尽量不要去使用 auto_ptr 管理内存资源。

三、unique_ptr

unique_ptr 是对 auto_ptr 的改进,它不再有所有权转移,它旨在独占所有权,即它只能有一个所有者。
下面来看一下简易版的 unique_ptr :

template<class T>
	class unique_ptr
	{
	public:
		unique_ptr(T *ptr):ptr_(ptr)
		{}
		~unique_ptr()
		{
			std::cout << "delete unique_ptr" << std::endl;
			delete ptr_;
		}
		T* operator->()
		{
			return ptr_;
		}
		T& operator*()
		{
			return *ptr_;
		}
	private:
		unique_ptr(unique_ptr<T> &up) = delete;
		unique_ptr<T> operator=(unique_ptr<T >& up) = delete;
	private:
		T* ptr_;
	};

简单粗暴的将拷贝构造和赋值运算符重载给 delete ,保证了所有权的唯一性。

但是,没了拷贝构造或赋值重载,这就意味着 unique_ptr 就没办法 “赋值” 给其他 unique_ptr 吗?事实并非如此, unique_ptr 内部还有一个移动构造与移动赋值函数,简化如下:

// 移动构造函数
unique_ptr(unique_ptr<T>&& up): ptr_(up.ptr_)
{
    up.ptr_ = nullptr; // 转移所有权
}

// 移动赋值操作符
unique_ptr<T>& operator=(unique_ptr<T>&& up)
{
    if (this != &up)
    {
        delete ptr_; // 删除当前持有的对象
        ptr_ = up.ptr_; // 接收新对象的所有权
        up.ptr_ = nullptr;
    }
    return *this;
}

 我们仍可以使用 unique_ptr<int> p2(std::move(p1)) 类似的操作来进行所有权的转移。

学习到了这里,你有没有想过,为什么 auto_ptr 一定要使用所有权转移的概念,为什么 unique_ptr 一定要独享所有权,不允许多个指针指向同一个对象?

以下是可能发生的危害:

假设 unique_ptr 支持拷贝构造或赋值重载,会带来以下几个问题:

  • 双重释放问题:当多个 unique_ptr 指向同一个对象时,当其中一个 unique_ptr 被销毁,它会释放所指向的对象;而其他指向相同对象的 unique_ptr 也会在各自销毁时再次尝试释放同一对象,导致程序崩溃。

  • 资源泄漏问题:如果 unique_ptr 被拷贝而没有转移所有权,多个指针都会管理同一个对象,当某个 unique_ptr 释放对象后,其他 unique_ptr 依然会尝试访问已被销毁的资源,导致悬空指针问题。

四、shared_ptr

shared_ptr的原理:是通过引用计数的方式来实现多个shared_ptr对象之间共享资源。

在 shared_ptr 中使用引用计数来管理资源,那么就需要使得指向同一份资源的指针可以访问同一块区域以调整引用计数,所以这里的计数器不能是简单的类成员,我们可以将它设置为指针,在不同 shared_ptr 使用时将指针赋值给它们。

同时,因为库中的 shared_ptr 使用互斥锁保证线程安全,我们这里也可以设置一个锁为成员变量,可以使用库中的 lock::guard ,类似于智能指针(RAII),可以自动解锁,在析构时也不需要单独释放锁:

	template<class T>
	class shared_ptr
	{
	public:
		shared_ptr(T* ptr):ptr_(ptr), pmtx_(new std::mutex), pCount_(new int(1))
		{}
		shared_ptr(const shared_ptr<T>& sp):ptr_(sp.ptr_), pmtx_(sp.pmtx_), pCount_(sp.pCount_)
		{
			AddRef();
		}
		~shared_ptr()
		{
			Release();
		}
	private:
		void AddRef()
		{
			std::lock_guard<std::mutex> lock(*pmtx_);  // 自动管理锁的释放
			(*pCount_)++;
		}

		void Release()
		{
			std::lock_guard<std::mutex> lock(*pmtx_);
			if (--(*pCount_) == 0) 
			{
				std::cout << "delete" << std::endl;
				delete ptr_;
				delete pCount_;
				delete pmtx_;
			}
		}

	private:
		T* ptr_;
		std::mutex *pmtx_;
		int* pCount_;
	};

但是, shared_ptr 还有一点不妥,当两个或以上的 shared_ptr 相互指向时,引用计数不会减为0,所以 C++11 还引入了 弱指针 weak_ptr 的概念,使用 weak_ptr 指向 shared_ptr 管理的对象不会增加 shared_ptr 的引用计数。

shared_ptr 可以隐式类型转换为 weak_ptr ,weak_ptr 可以通过它的成员函数 lock() 转化为 weak_ptr ,示例:

std::shared_ptr<int> sp = std::make_shared<int>(10);
std::weak_ptr<int> wp = sp;// shared_ptr 可以直接隐式类型转化为 weak_ptr

if (std::shared_ptr<int> locked_sp = wp.lock())// weak_ptr通过使用lock转化为shared_ptr
{
    // 成功获取 shared_ptr,安全使用对象
    std::cout << *locked_sp << std::endl;
}
else
{
    // 对象已经被销毁
    std::cout << "The object is no longer available." << std::endl;
}

http://www.kler.cn/a/305282.html

相关文章:

  • js 获取某日期到现在的时长 js 数字补齐2位
  • 【OceanBase 诊断调优】—— ocp上针对OB租户CPU消耗计算逻辑
  • 21. Drag-Drop拖放操作(二) - 文件、表格和树的拖放实现
  • 【Webpack实用指南】如何拆分CSS资源(2)
  • 网站小程序app怎么查有没有备案?
  • request爬虫库的小坑
  • CH1-1 引论
  • Rust:Result 和 Error
  • 职场 Death Note
  • Uniapp + Vue3 + Vite +Uview + Pinia 实现提交订单以及支付功能(最新附源码保姆级)
  • MATLAB中who的用法
  • flink增量检查点启动恢复的时间是很久的,业务上不能接受,怎么处理
  • MySQL索引-聚簇索引和非聚簇索引
  • 【Python机器学习】循环神经网络(RNN)——传递数据并训练
  • flask中安全策略简要说明
  • 景联文科技:专业扫地机器人数据采集标注服务
  • C/C++动态库函数导出 windows
  • Python数据分析 Pandas库-初步认识
  • Spring Boot-版本兼容性问题
  • 用 SQL 写的俄罗斯方块游戏「GitHub 热点速览」
  • Nginx:高性能的Web服务器与反向代理
  • 矩阵直播换IP:如何使用代理IP提升直播效果
  • java enum code-label模式的使用方法
  • MATLAB算法实战应用案例精讲-【人工智能】数据血缘分析(概念篇)
  • 计算机视觉学习路线(纯纯小白)
  • idea开发Java程序的步骤及设置