智能指针(RAII)
- 一、内存泄漏
- 1、介绍
- 2、原因
- 3、泄漏的内存类型分类
- 二、RAII
- 1、介绍
- 2、基本思想
- 3、优点
- 4、实现方式
- 三、unique_ptr
- 1、介绍
- 2、主要特性
- 3、注意事项
- 4、unique_ptr类
- 5、示例代码
- 6、运行结果
- 7、简单实现
- 四、shared_ptr
- 1、介绍
- 2、主要特点
- 3、注意事项
- 4、shared_ptr类
- 5、简单实现
- 五、weak_ptr
- 1、介绍
- 2、weak_ptr类
- 3、shared_ptr的循环引用问题
- 4、循环引用示例代码
一、内存泄漏
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类型。
本文到这里就结束了,如有错误或者不清楚的地方欢迎评论或者私信
创作不易,如果觉得博主写得不错,请点赞、收藏加关注支持一下💕💕💕