C++中的智能指针是一种用于自动管理动态分配内存的对象。通过使用智能指针,开发者可以避免由于忘记释放内存而导致的内存泄漏,以及由于多次释放同一块内存而导致的双重释放问题。
C++标准库提供了几种不同类型的智能指针,包括std::unique_ptr、std::shared_ptr、std::weak_ptr和std::auto_ptr(但std::auto_ptr在C++11及以后的版本中已被废弃)。
std::unique_ptr的主要特点是“独占所有权”,在给定时间内只有唯一一个指针可以拥有对象,当这个指针被销毁(例如,离开其作用域)时,它所指向的对象也会被自动删除。
移动语义:允许资源的所有权从一个std::unique_ptr对象转移到另一个std::unique_ptr对象,通过std::move函数实现;
空指针检查:std::unique_ptr提供了成员函数get方法来获取所管理的指针,如果是空指针则表示不拥有任何资源;
自定义删除器:std::unique_ptr允许用户指定一个自定义的删除器(可以是一个函数、函数对象或 lambda 表达式),用于在资源释放时执行特定的操作;
内部实现:std::unique_ptr是一个模板类,定义在
std::unique_ptrdecltype (deleter_wrapper)> ptr(new MyClass(42), deleter_wrapper);
使用技巧: 在创建std::unique_ptr时推荐使用std::make_unique函数来替代直接调用构造函数。
- // 当ptr离开作用域时,int对象会被自动删除
- std::unique_ptr<int> ptr(new int(5)); // 直接使用new运算符构造
- std::unique_ptr<int> ptr = std::make_unique<int>(42); // 使用std::make_unique构造
std::make_unique 是 C++14 引入的一个智能指针辅助函数,它用于动态分配一个对象并初始化一个 std::unique_ptr 智能指针来管理这个对象的生命周期。使用 std::make_unique 比直接使用 new 运算符并构造 std::unique_ptr 更为安全和高效,因为它可以将对象和其控制块分配在连续的内存中。如果在构造函数内部抛出了异常,std::make_unique 会确保内存被正确释放,从而避免了内存泄漏。
std::shared_ptr的主要特点是“共享所有权”。它使用一个引用计数机制来管理资源的生命周期,确保当没有任何std::shared_ptr指向该资源时,资源会被自动释放。
引用计数:每个std::shared_ptr对象都持有一个指向控制块的指针,这个控制块包含一个引用计数和指向被管理资源的指针。当一个新的std::shared_ptr被创建和引用赋值时,控制块的引用计数会增加。当一个std::shared_ptr被销毁或被重置为另一个资源时,控制块的引用计数会减少。
控制块:控制块包含一个引用计数和指向被管理资源的指针,还包含删除器和自定义分配器的指针,引用计数变为0时释放资源。
自定义删除器:std::shared_ptr允许用户提供一个自定义的删除器,该删除器在引用计数为0时调用,用于释放资源。
线程安全:std::shared_ptr的引用计数是线程安全的,引用计数的增加和减少操作通常使用原子操作来实现。
循环引用:std::shared_ptr可以有效地管理资源的生命周期,但它无法解决循环引用问题。为解决循环引用问题引入了std::weak_ptr。
- std::shared_ptr<int> ptr1 = std::make_shared<int>(5);
- std::shared_ptr<int> ptr2 = ptr1; // ptr1和ptr2共享所有权
- // 当ptr1和ptr2都离开作用域时,int对象才会被删除
std::weak_ptr是一种弱引用智能指针,不会增加对象的引用计数,也不影响对象的声明周期。
内部结构:std::weak_ptr内部维护了一个指向引用计数对象的指针,这个引用计数对象与std::shared_ptr共享。引用计数对象包含两个重要的计数:强引用计数(由std::shared_ptr管理)和弱引用计数(由std::weak_ptr管理)。
主要操作:std::weak_ptr 可以从 std::shared_ptr 或另一个 std::weak_ptr 构造,构造过程中不会增加强引用计数,但会增加弱引用计数。std::weak_ptr 提供了 lock 成员函数,用于尝试获取一个指向同一对象的 std::shared_ptr。std::weak_ptr 提供了 expired 成员函数,用于检查所指向的对象是否已经被销毁。如果对象已经被销毁,expired 返回 true;否则返回 false。
生命周期:当 std::shared_ptr 释放了所管理的对象时,对象的强引用计数会变为 0,对象会被删除。但此时弱引用计数可能不为 0(因为有 std::weak_ptr 指向它),因此对象的控制块(包含引用计数信息的结构)不会被立即销毁。只有当最后一个指向该对象的 std::weak_ptr 被销毁时,弱引用计数才会变为 0,此时控制块才会被销毁,从而完全释放资源。
注意事项:避免过度使用 lock 函数,因为它可能会增加引用计数并进行内存分配,从而影响性能。在使用 std::weak_ptr 观测所指向对象的生命周期时,应该始终使用 lock 或 expired 函数来检查对象是否存在,以避免在访问已销毁对象时产生未定义行为。
- // 假设有两个shared_ptr相互引用,形成循环引用
- // 使用weak_ptr可以打破这种循环引用
std::auto_ptr是C++98中引入的一个智能指针,但在C++11及以后的版本中已被废弃,因为它存在所有权转移的问题,可能导致意外的行为。因此,建议不要使用std::auto_ptr。