C++智能指针

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;

}

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

gsfl

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值