智能指针(RAII)

一、内存泄漏

1、介绍

  • 内存泄漏(Memory Leak)是计算机科学中的一个常见问题,指的是程序在运行过程中,未能释放那些已经不再使用的内存空间。随着时间的推移,这些未释放的内存会逐渐累积,最终导致程序可用的内存越来越少,可能引发程序运行缓慢、崩溃或者系统资源耗尽等问题。

2、原因

  • 申请内存成功,相关工作结束后不释放这些内存,这是最常见的内存泄漏原因。
  • 错误的内存管理逻辑,如重复释放内存(double free)、未检查内存是否分配成功就使用等等。
  • 缓存策略不当,在某些情况下,程序会缓存数据以提高性能,但如果缓存策略不好,如没有适当的清理机制,就可能导致内存泄漏。
  • 使用的第三方库或框架可能存在内存泄漏的问题。
  • 作用域问题,如果局部变量(如动态分配的对象)的作用域过大,且未在使用完毕后及时释放,可能导致内存泄漏。

3、泄漏的内存类型分类

  • 堆内存泄漏:堆内存指的是程序执行过程中通过malloc、calloc、realloc(C语言)或new(C++语言)等函数从堆上分配的内存。如果这部分内存没有被正确地释放(如忘记调用free或delete),就会导致堆内存泄漏。
  • 系统资源泄漏:程序使用系统分配的其他资源,如文件描述符、套接字、数据库连接等等。如果这些资源在使用完毕后没有被正确地释放,如忘记关闭文件、断开网络连接等等,就会导致系统资源泄漏。

二、RAII

1、介绍

  • RAII(Resource Acquisition Is Initialization)是一种在C++编程中广泛使用的资源管理技术。它基于C++的构造函数和析构函数的调用机制,确保资源(动态分配的内存、文件句柄、网络连接等等)在对象生命周期内被正确地获取和释放。

2、基本思想

  • 资源的获取(Acquisition) 与对象的初始化(Initialization) 绑定在一起。这意味着,当对象被创建时,它会自动获取所需的资源。
  • 资源的释放(Release) 与对象的析构(Destruction) 绑定在一起。这意味着,当对象的生命周期结束时,例如,对象被销毁或作用域结束,它的析构函数会自动释放它所管理的资源。

3、优点

  • 自动管理资源:RAII通过自动调用构造函数和析构函数来管理资源,减少了手动管理资源的复杂性和出错的可能性。
  • 异常安全:在C++中,异常可能会导致函数提前退出。使用RAII,即使发生异常,对象的析构函数也会被调用,从而确保资源被正确地释放。
  • 减少内存泄漏:由于资源在对象析构时自动释放,因此可以显著减少内存泄漏的风险。

4、实现方式

  • RAII通常通过封装资源的类来实现。这些类通常包含以下部分。
  • 构造函数:在构造函数中,类会获取所需的资源。
  • 析构函数:在析构函数中,类会释放它所管理的资源。
  • 拷贝构造函数和赋值运算符重载(可选,但通常需要禁用或适当实现,以防止资源被错误地复制或赋值):如果对象包含独占资源(如文件句柄),则应该禁用拷贝构造函数和赋值运算符重载,或者实现深拷贝以确保资源被正确管理。
  • 关于对应函数的介绍参见类与对象(中)

三、unique_ptr

1、介绍

  • unique_ptr 是C++11引入的一个智能指针,用于管理动态分配的内存,确保资源(如动态分配的对象、数组等)在不再需要时能够被自动释放。
  • 与shared_ptr不同,unique_ptr拥有其所指对象的唯一所有权,即两个unique_ptr不能同时指向同一个对象。

2、主要特性

  • 唯一所有权:unique_ptr不允许复制(copy),但支持移动(move),这保证了在任何时候只有一个unique_ptr实例拥有对资源的所有权。
  • 自动释放:当unique_ptr的实例被销毁时(例如,离开作用域时),它会自动调用其指向对象的析构函数(如果是对象的话),并释放分配的内存。
  • 自定义删除器:unique_ptr允许指定一个自定义的删除器,该删除器将在unique_ptr被销毁时调用,以执行资源的释放逻辑。
  • 数组支持:unique_ptr有两个特化版本,一个用于单个对象,另一个用于对象数组。对于数组版本,unique_ptr会调用delete[]而不是delete来释放内存。

3、注意事项

  • 由于unique_ptr不支持复制,因此不能直接将一个unique_ptr赋值给另一个同类型的unique_ptr,否则会导致编译错误。但是,可以使用move来转移所有权。
  • 当使用unique_ptr管理动态分配的数组时,需确保使用unique_ptr<T[]>的特化版本,以便正确调用delete[]而不是delete。
  • unique_ptr是轻量级的,并且通常比shared_ptr有更好的性能,因为它不涉及额外的开销(如控制块)来管理共享所有权。然而,这也意味着它不能用于需要在多个所有者之间共享资源的场景。

4、unique_ptr类

在这里插入图片描述

5、示例代码

class Test_unique_ptr_Class {
public:
	Test_unique_ptr_Class() { std::cout << "MyClass created\n"; }
	~Test_unique_ptr_Class() { std::cout << "MyClass destroyed\n"; }
	void Func() { std::cout << "Doing something...\n"; }
};

void Test_unique_ptr1()
{
	std::unique_ptr<Test_unique_ptr_Class> ptr = std::make_unique<Test_unique_ptr_Class>();
	ptr->Func();
}

void Test_unique_ptr2()
{
	std::unique_ptr<int[]> arr = std::make_unique<int[]>(4);
	for (int i = 0; i < 4; ++i) {
		arr[i] = i * i;
	}
}

int main()
{
	Test_unique_ptr1();
	Test_unique_ptr2();

	return 0;
}

6、运行结果

在这里插入图片描述

7、简单实现

template<class T>
class unique_ptr
{
public:
	unique_ptr(T* ptr)
		:_ptr(ptr)
	{}

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

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

	T* operator->()
	{
		return _ptr;
	}
	
	unique_ptr(const unique_ptr<T>& up) = delete;
	unique_ptr<T>& operator=(const unique_ptr<T>& up) = delete;
	
private:
	T* _ptr;
};

四、shared_ptr

1、介绍

  • shared_ptr是C++标准库中的一个智能指针模板类,用于自动管理具有共享所有权的动态分配的对象。通过使用shared_ptr可以避免手动管理内存(如使用new和delete),从而减少内存泄漏的风险。

2、主要特点

  • 共享所有权:多个shared_ptr实例可以指向同一个对象。当最后一个指向该对象的shared_ptr被销毁或重置时,对象会被自动删除。
  • 引用计数:shared_ptr使用一个内部计数器来跟踪有多少个shared_ptr实例指向某个对象。每当一个新的shared_ptr被创建并指向该对象时,计数器递增;每当一个shared_ptr被销毁或重置时,计数器递减。当计数器达到零时,对象被删除。
  • 线程安全:shared_ptr的引用计数操作是线程安全的,允许多个线程安全地共享对象。但是,这并不意味着指向的对象本身是线程安全的;如果多个线程需要修改对象的状态,则需要额外的同步机制。

3、注意事项

  • 性能开销:虽然shared_ptr提供了方便的内存管理,但它也引入了额外的性能开销(如引用计数的维护)。在性能敏感的应用中,需要权衡使用shared_ptr的利弊。
  • 自定义删除器:shared_ptr允许指定一个自定义的删除器,用于在对象被销毁时执行特定的清理操作。这可以通过传递一个删除器给shared_ptr的构造函数来实现。

4、shared_ptr类

在这里插入图片描述
在这里插入图片描述

5、简单实现

template<class T>
class shared_ptr
{
public:
	shared_ptr(T* ptr = nullptr)
		:_ptr(ptr)
		, _pcount(new atomic<int>(1))
	{}

	template<class D>
	shared_ptr(T* ptr, D del)
		:_ptr(ptr)
		,_del(del)
		, _pcount(new atomic<int>(1))
	{}

	void Release()
	{
		if (--(*_pcount) == 0)
		{
			_del(_ptr);
			delete _pcount;
			_ptr = nullptr;
			_pcount = nullptr;
		}
	}

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

	shared_ptr<T>& operator=(const shared_ptr<T>& sp)
	{
		if (_ptr != sp._ptr)
		{
			Release();
			_ptr = sp._ptr;
			_pcount = sp._pcount;
			++(*_pcount);
		}
		return *this;
	}

	~shared_ptr()
	{
		Release();
	}

	T* GetPtr() const
	{
		return _ptr;
	}

	int GetUseCount()	const
	{
		return *_pcount;
	}

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

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

private:
	T* _ptr;
	atomic<int>* _pcount;
	function<void(T*)> _del = [](T* ptr) {delete ptr; };
};

五、weak_ptr

1、介绍

  • weak_ptr是C++11引入的一种智能指针,它用于解决shared_ptr之间的循环引用问题。当两个或多个shared_ptr相互引用时,就会形成一个循环引用,导致这些shared_ptr所指向的对象无法被正确删除,因为每个shared_ptr都认为至少还有一个其他shared_ptr指向该对象。而weak_ptr的设计就是为了解决这个问题。
  • weak_ptr不拥有其所指向的对象,它不会增加对象的所有权计数,即shared_ptr中的引用计数。相反,weak_ptr提供了一种方式来观察shared_ptr所管理的对象,但不阻止该对象被销毁。
  • weak_ptr可以从shared_ptr或另一个weak_ptr中创建,但不能直接用来访问对象;它必须首先被锁定为一个shared_ptr,这通过调用weak_ptr的lock()方法实现。如果原始shared_ptr已经被销毁,lock()将返回一个空的shared_ptr。

2、weak_ptr类

在这里插入图片描述
在这里插入图片描述

3、shared_ptr的循环引用问题

  • 在C++中,当使用shared_ptr时,一个常见的问题是循环引用(circular reference),这发生在两个或多个shared_ptr实例相互引用对方。由于每个shared_ptr都认为至少有一个其他shared_ptr正在引用其管理的对象,因此它们的引用计数永远不会降为零,这导致内存无法被正确释放,从而产生内存泄漏。

4、循环引用示例代码

class B;

class A {
public:
	//std::shared_ptr<B> bPtr;
	std::weak_ptr<B> bPtr;
	~A() { std::cout << "A destroyed\n"; }
};

class B {
public:
	std::shared_ptr<A> aPtr;
	~B() { std::cout << "B destroyed\n"; }
};

void Test_shared_ptr5()
{
	std::shared_ptr<A> a = std::make_shared<A>();
	std::shared_ptr<B> b = std::make_shared<B>();

	// 创建循环引用  
	a->bPtr = b;
	b->aPtr = a;
}
  • 上方代码为循环引用问题解决后的代码,循环引用问题的代码为将A类中的bPtr改为shared_ptr类型。

本文到这里就结束了,如有错误或者不清楚的地方欢迎评论或者私信
创作不易,如果觉得博主写得不错,请点赞、收藏加关注支持一下💕💕💕

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值