首页 > 编程语言 >C++智能指针

C++智能指针

时间:2024-08-26 13:58:01浏览次数:15  
标签:Node auto C++ 智能 shared unique ptr 指针

文章目录

RAII

RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内
存、文件句柄、网络连接、互斥量等等)的简单技术。
在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在
对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。这种做
法有两大好处:
不需要显式地释放资源。
采用这种方式,对象所需的资源在其生命期内始终保持有效。

auto_ptr

C++98版本的库中就提供了auto_ptr的智能指针。下面演示的auto_ptr的使用及问题。
auto_ptr的实现原理:管理权转移的思想,下面简化模拟实现了一份bit::auto_ptr来了解它的原

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对象拥有对动态分配对象的唯一所有权。当auto_ptr被销毁时,它会自动释放所拥有的对象。
然而,auto_ptr存在一些严重的问题,限制了它的使用:

所有权转移:当auto_ptr被拷贝或赋值时,所有权会从源对象转移到目标对象,源对象会变成空指针。这种特性在函数参数传递和返回值时尤其需要注意,因为它可能导致意外的资源释放或悬空指针。
在这里插入图片描述
在拷贝构造时,原指针被置空,如果后续代码仍然尝试访问原 auto_ptr 对象所指向的内存,这将导致悬空指针问题,进而可能引发程序崩溃或不可预测的行为。

不支持数组:auto_ptr不能用于管理动态分配的数组,因为它使用delete而不是delete[]来释放资源,这会导致未定义行为。
不支持STL容器:由于auto_ptr的拷贝行为(所有权转移),它不适合作为STL容器的元素,因为容器在内部可能会进行元素的拷贝或赋值操作。

unique_ptr

为了解决auto_ptr的问题,C++11中开始提供更靠谱的unique_ptr
std::unique_ptr也是独占所有权的智能指针,同时禁止拷贝操作,提供了移动操作,允许在需要时转移所有权,这种设计避免了意外的所有权转移和相关的风险。

unique_ptr还提供了对数组的支持。通过指定模板参数为 T[],unique_ptr 可以使用 delete[] 来正确释放数组资源。

支持STL容器

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

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;
 };
}

在这里插入图片描述
通过将拷贝构造函数和拷贝赋值运算符声明为 delete,禁止了 unique_ptr 的拷贝操作。这是 unique_ptr 设计中的一个关键部分,因为它确保了资源的独占所有权,并防止了由于拷贝操作而导致的所有权转移问题。

share_ptr

C11还提供了share_ptr和weak_ptr
std::shared_ptr允许多个智能指针共同拥有同一个资源。它们通过内部的计数器来跟踪有多少个std::shared_ptr实例指向该资源。当最后一个std::shared_ptr被销毁或重置时,资源才会被释放。

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

可以通过use.count()函数来查看当前资源的引用计数

struct Node
{
	shared_ptr<Node> _next;
	shared_ptr<Node> _prev;
	int _val;

	~Node()
	{
		cout << "~Node()" << endl;
	}
};

int main()
{
	shared_ptr<Node>p1(new Node);
	shared_ptr<Node>p2(new Node);

	cout << p1.use_count() << endl;
    cout << p2.use_count() << endl;

	p1 = p2;

	cout << p1.use_count() << endl;
	cout << p2.use_count() << endl;

	return 0;

}

在这里插入图片描述

shared_ptr的线程安全问题

智能指针(如C++中的std::shared_ptr、std::unique_ptr等)主要用于自动管理堆上分配的内存,确保资源在不再需要时能够被正确释放,从而避免内存泄漏。然而,智能指针本身并不解决多线程环境下的线程安全问题。

当两个或多个线程同时访问由智能指针管理的堆上对象时,如果这些访问涉及到对对象状态的修改(即非只读访问),就可能发生数据竞争(data race),这是一种未定义行为,可能导致程序崩溃、数据损坏或不可预测的行为。

为了在多线程环境中安全地共享由智能指针管理的对象,需要采取额外的同步措施,例如使用互斥锁(mutexes)、读写锁(reader-writer locks)、原子操作(atomic operations)或其他同步机制来确保对共享资源的访问是互斥的。
可以参考C++线程中的原子操作。

shared_ptr循环引用

struct Node
{
	shared_ptr<Node> _next;
	shared_ptr<Node> _prev;
	int _val;

	~Node()
	{
		cout << "~Node()" << endl;
	}
};

int main()
{
	shared_ptr<Node>node1(new Node);
	shared_ptr<Node>node2(new Node);

	cout << node1.use_count() << endl;
    cout << node2.use_count() << endl;

	node1->_next = node2;
	node2->_prev = node1;

	cout << node1.use_count() << endl;
	cout << node2.use_count() << endl;

	return 0;

}

在这里插入图片描述
分析:
看运行结果发现没有调用析构函数。
node1和node2两个智能指针对象指向两个节点,引用计数变成1。
node1的_next指向node2,node2的_prev指向node1,引用计数变成2。

由于相互指向的关系,_next析构了,node2就释放了,_prev析构了,node1就释放了。

在这里插入图片描述

但是_next属于node的成员,node1释放了,_next才会析构,而node1由_prev管理,_prev属于node2成员,所以这就叫循环引用,谁也不会释放。

为了解决循环引用的问题,我们引用了weak_ptr。

weak_ptr

std::weak_ptr是一种不拥有其所指向对象的智能指针,它主要用来解决std::shared_ptr可能引起的循环引用问题。它必须和一个std::shared_ptr一起使用,但不能单独用来管理资源。

weak_ptr的_next和_prev不会增加node1和node2的引用计数。

struct Node
{
	weak_ptr<Node> _next;
	weak_ptr<Node> _prev;
	int _val;

	~Node()
	{
		cout << "~Node()" << endl;
	}
};

int main()
{
	shared_ptr<Node>node1(new Node);
	shared_ptr<Node>node2(new Node);

	cout << node1.use_count() << endl;
    cout << node2.use_count() << endl;

	node1->_next = node2;
	node2->_prev = node1;

	cout << node1.use_count() << endl;
	cout << node2.use_count() << endl;

	return 0;

}

在这里插入图片描述

标签:Node,auto,C++,智能,shared,unique,ptr,指针
From: https://blog.csdn.net/2301_79274600/article/details/141547190

相关文章

  • 学懂C++(四十四):C++ 自定义内存管理的深入解析:内存池与自定义分配器
    目录1.内存池(MemoryPool)概念模型特点核心点实现适用场景经典示例实现代码解析2.自定义分配器(CustomAllocators)概念模型特点核心点实现适用场景经典示例实现代码解析高级自定义分配器示例代码解析总结        C++作为一种高性能编程语言,在......
  • 算法:双指针
    题目:复写零虽然题目说必须要就地但是我们可以先试试异地然后再想办法优化成本地算法讲解:异地可以定义两个指针让它们分别指向本地和异地,当本地指针指向零时这时候就往异地写入两个零其余就照常写,说完异地做法那我们应该如何优化成就地做法呢?就地本地也要定义两个指针往......
  • c++关键字
    关键字作用:关键字是C++中预先保留的单词(标识符)在定义变量或者常量时候,不要用关键字C++关键字如下:关键字1.asmasm(指令字符串):允许在C++程序中嵌入汇编代码。2.autoauto(自动,automatic)是存储类型标识符,表明变量"自动"具有本地范围,块范围的变量声明(如for循环体内的......
  • 【C++】初识C++模板与STL
    C++语法相关知识点可以通过点击以下链接进行学习一起加油!命名空间缺省参数与函数重载C++相关特性类和对象-上篇类和对象-中篇类和对象-下篇日期类C/C++内存管理本章将简单分享C++模板与STL相关知识,与之相关更多知识将留到下次更详细地来分享给大家......
  • vscode 编译c++项目如何配置
    配置c_cpp_properties.json文件主要用于辅助vscode智能代码提示、预定义编译宏定义示例如下:{"configurations":[{"name":"Win32","includePath":["${workspaceFolder}/**",......
  • 微软常用运行库合集|dll报错必装,Visual C++ 下载安装
    前言MicrosoftVisualC++Redistributable(简称MSVC,VB/VC,系统运行库)是Windows操作系统应用程序的基础类型库组件。此版VisualC++运行库组件合集(微软常用运行库合集)由国内封装爱好者@Dreamcast打包而成,整合VisualC++组件安装包运行库所有版本,提供图形安装界面,可自选更新V......
  • 2024年智能革命:HarmonyOS NEXT与盘古大模型5.0的颠覆性融合
    引言2024年,这一年注定在全球智能设备市场的历史上写下浓墨重彩的一笔。作为全球科技巨头,华为再次以其前瞻性的布局,推动了技术与应用的深度融合。在这个充满变革的时代,华为通过不断扩展的鸿蒙生态系统,重新定义了操作系统与AI技术的结合方式。你是否已经感受到这场变革的力量?在全......
  • C++学习随笔——简单的单例设计模式实例
    点击查看代码#include<iostream>classSingleton{private://私有化构造函数,防止外部实例化Singleton(){std::cout<<"SingletonInstanceCreated!"<<std::endl;}//删除拷贝构造函数和赋值运算符,防止拷贝实例Singleton(constSin......
  • 深入理解指针(下)
    1.数组名的理解首先分析下面这段代码intarr[10]={1,2,3,4,5,6,7,8,9,10};int*p=&arr[0];这里我们使用 &arr[0]的方式拿到了数组第⼀个元素的地址,但是其实数组名本来就是地址,而且是数组首元素的地址,我们来做个测试。输出结果为我们发现......
  • 47.【C语言】指针(重难点)(J)
    目录26.自制排序函数(★★)    *分析    *代码往期推荐26.自制排序函数*分析之前在42.【C语言】冒泡排序写过一个排序函数,可以将此自制一个类似qsort的函数画圈的地方是需要修改的#include<stddef.h>voidbubble_sort(void*base,size_tnum,size_tw......