【C++】智能指针

unique_ptr

C++11引入了std::unique_ptr主要是为了提供一种在资源管理方面更为安全和高效的替代方案。std::unique_ptr是一种独占所有权的智能指针,它确保在程序的生命周期内只有一个指针可以拥有和管理某个资源,通常是动态分配的对象或数组。

std::unique_ptr特点:

  1. 独占所有权: std::unique_ptr不允许多个指针共享同一个资源,这意味着在任意时刻只有一个unique_ptr可以拥有资源。这有助于防止资源泄漏和难以调试的所有权问题。

  2. 自动资源释放:std::unique_ptr的生命周期结束时(例如,离开其作用域),它会自动释放所拥有的资源,无需手动调用deletedelete[]。这有助于避免内存泄漏,并简化了资源管理。

  3. 移动语义: std::unique_ptr支持移动语义,允许资源的高效转移。这使得在函数之间传递std::unique_ptr更为高效,避免了不必要的资源复制。

  4. 定制删除器: 可以通过指定删除器(deleter)来自定义资源的释放行为。这使得std::unique_ptr非常灵活,能够管理各种类型的资源。

  5. NULL指针检查: std::unique_ptr提供了对空指针的检查,可以帮助避免潜在的运行时错误。

成员函数及其说明:

  1. 构造函数:

    • std::unique_ptr<T>:默认构造函数,创建一个空的 std::unique_ptr
    • std::unique_ptr<T>(T* ptr):接受一个原始指针并拥有它的所有权。
    • std::unique_ptr<T, Deleter>(T* ptr, Deleter d):使用指定的删除器创建 std::unique_ptr
  2. 无法进行复制构造和赋值操作,支持移动语义:

    • std::unique_ptr<T>(std::unique_ptr<T>&& other):移动构造函数,允许将资源的所有权从一个 std::unique_ptr 转移到另一个。
    • std::unique_ptr<T>& operator=(std::unique_ptr<T>&& other):移动赋值操作符。
  3. 释放资源:

    • T* release():释放对资源的所有权,并返回原始指针。
    • void reset():释放当前指针拥有的资源,并将指针置为空。
    • void reset(T* ptr):释放当前指针拥有的资源,并接管参数指针的所有权。
  4. 获取指针信息:

    • T* get():返回原始指针。
    • T& operator*():解引用操作符,返回指向的对象的引用。
    • T* operator->():成员访问操作符,用于通过 std::unique_ptr 访问对象的成员。
  5. 检查空指针:

    • bool operator==(std::nullptr_t) const noexcept:检查是否为 nullptr。
    • explicit operator bool() const noexcept:将 std::unique_ptr 转换为布尔值,检查是否拥有资源。
  6. 自定义删除器:

    • template <class Deleter> std::unique_ptr(T* ptr, Deleter d):使用自定义删除器创建 std::unique_ptr
    • template <class Deleter> Deleter& get_deleter() noexcept:获取当前删除器。

使用示例

  1. 创建unique_ptr
c 复制代码
      // 构造方式
      std::unique_ptr<int> p1();
      std::unique_ptr<int> p2(nullptr);
  1. 无法进行复制构造和赋值操作
c 复制代码
      std::unique_ptr<int> p3(new int);
      std::unique_ptr<int> p4(p3);
      std::unique_ptr<int> p5(p3);
      
  1. 支持移动语义
c 复制代码
      std::unique_ptr<int> p6(new int);
      std::unique_ptr<int> p7(std::move(p6));
  1. 释放当前资源,设置为空指针
c 复制代码
      std::unique_ptr<int> p8(new int);
      std::cout << "before address: " << p8.get() << std::endl;
      p8.reset();
      std::cout << "after address: " << p8.get() << std::endl;

输出

c 复制代码
before address: 0x2588030
after address: 0
  1. 解引用
c 复制代码
      std::unique_ptr<int> p9(new int(6));
      std::cout << "*p9 = " << *p9 << std::endl;

输出

c 复制代码
*p9 = 6
  1. 释放资源的所有权,返回原始指针的引用
c 复制代码
      std::unique_ptr<int> p10(new int(10));
      int *p11 = p10.release();
      std::cout << "*p11 = " << *p11 << std::endl;

shared_ptr

c 复制代码
每个shared_ptr都有一个关联的计数值,通常称为引用计数。无论何时我们拷贝一个shared_ptr,计数器都会递增。
例如,当用一个shared_ptr初始化另一个shred_ptr,或将它当做参数传递给一个函数以及作为函数的返回值时,它
所关联的计数器就会递增。当我们给shared_ptr赋予一个新值或是shared_ptr被销毁(例如一个局部的
shared_ptr离开其作用域)时,计数器就会递减。一旦一个shared_ptr的计数器变为0,它就会自动释放自己所管理
的对象。

成员函数线程安全,非成员函数线程不安全。成员函数线程安全通过std::atomic::fetch_add with std::memory_order_relaxed 来实现。

std::shared_ptr

std::shared_ptr特点:

  1. 共享所有权: std::shared_ptr 允许多个智能指针共享同一个资源,通过引用计数来追踪资源的所有权。当最后一个指向资源的 std::shared_ptr 被销毁或重置时,资源才会被释放。

  2. 引用计数: std::shared_ptr 内部维护了一个引用计数,用于记录有多少个智能指针共享同一个资源。引用计数通过原子操作进行增加和减少,从而确保在多线程环境下的安全性。

  3. 弱引用支持: std::shared_ptr 配合 std::weak_ptr 提供了弱引用的支持,可以用于解决循环引用的问题,避免内存泄漏。

  4. 自动释放资源: 当最后一个拥有资源的 std::shared_ptr 被销毁时,它会自动释放所拥有的资源,无需手动调用 delete

  5. 空指针检查: 提供了安全的空指针检查,避免了访问空指针的运行时错误。

主要成员函数

  1. 构造函数:

    • std::shared_ptr<T>():默认构造函数,创建一个空的 std::shared_ptr
    • std::shared_ptr<T>(T* ptr):接受一个原始指针并拥有它的所有权。
    • std::shared_ptr<T>(const std::shared_ptr<T>& other):复制构造函数,共享资源。
    • std::shared_ptr<T>(std::nullptr_t):创建一个空的 std::shared_ptr
  2. 引用计数相关:

    • long use_count() const noexcept:返回当前共享资源的引用计数。
    • bool unique() const noexcept:检查是否是唯一拥有资源的 std::shared_ptr
  3. 移动语义:

    • std::shared_ptr<T>(std::shared_ptr<T>&& other):移动构造函数,将资源的所有权从一个 std::shared_ptr 转移到另一个。
  4. 赋值操作符:

    • std::shared_ptr<T>& operator=(const std::shared_ptr<T>& other):复制赋值操作符,共享资源。
    • std::shared_ptr<T>& operator=(std::shared_ptr<T>&& other):移动赋值操作符。
  5. 获取指针信息:

    • T* get():返回原始指针。
    • T& operator*():解引用操作符,返回指向的对象的引用。
    • T* operator->():成员访问操作符,用于通过 std::shared_ptr 访问对象的成员。
  6. 空指针检查:

    • bool operator==(std::nullptr_t) const noexcept:检查是否为 nullptr。
    • explicit operator bool() const noexcept:将 std::shared_ptr 转换为布尔值,检查是否拥有资源。
  7. 自定义删除器:

    • template <class Deleter> std::shared_ptr(T* ptr, Deleter d):使用自定义删除器创建 std::shared_ptr
    • template <class Deleter> Deleter& get_deleter() noexcept:获取当前删除器。

创建实例

通过make_shared 或者 new 来构造实例

c 复制代码
      shared_ptr<int> p1 = make_shared<int>(12345);
      cout << "p1: " << *p1 << endl;

      shared_ptr<int> p2(new int(23456));
      cout << "p2: " << *p2 << endl;

new 和 make_shared的区别: new方法在堆上创建了两块内存:1:存储int。2:控制块上用于引用计数的内存,管理附加此内存的 shared_ptr 对象的计数,最初计数将为1。 std::make_shared 一次性为int对象和用于引用计数的数据都分配了内存,而new操作符只是为int分配了内存。

性能差异:

  • 性能: std::make_shared 的性能可能更好,因为它可以一次性分配内存来存储对象和控制块,减少了额外的动态内存分配。
  • 异常安全性: std::make_shared 在分配内存时提供了强烈的异常安全性,因为它只有在成功分配了所有内存时才会构造对象。而直接使用构造函数可能在分配内存后抛出异常,导致资源泄漏。
  1. 通过拷贝构造初始化,会增加引用计数值
c 复制代码
      shared_ptr<int> p2(new int(23456));
      cout << "p2: " << *p2 << endl;


      shared_ptr<int> p3(p2);
      cout << "p2: " << p2.use_count() << endl;
      cout << "p3: " << p3.use_count() << endl;
  1. 当share_ptr实例p和实例q,指向相同的类型,通过赋值操作 p = q ,会减少 p 的引用计数, 增加 q 的引用计数。
c 复制代码
  class Example
  {
  public:
      Example(const char *name) : e(1) { cout << "Example Constructor..." << name << endl; }
      ~Example() { cout << "Example Destructor..." << endl; }

      int e;
  };

  int main() {

      shared_ptr<Example> p4(new Example("a"));
      shared_ptr<Example> p5(new Example("b"));
      cout << "p4: " << p4.use_count() << endl;
      cout << "p5: " << p5.use_count() << endl;

      p4 = p5;
      cout << "p4: " << p4.use_count() << endl;
      cout << "p5: " << p5.use_count() << endl;


  }

输出:

c 复制代码
Example Constructor...a
Example Constructor...b
p4: 1
p5: 1
Example Destructor...
p4: 2
p5: 2
Example Destructor...
  1. 检查引用计数 调用use_count返回引用计数值
c 复制代码
      shared_ptr<int> p3(p2);
      cout << "p2: " << p2.use_count() << endl;
      cout << "p3: " << p3.use_count() << endl;
相关推荐
无 证明4 分钟前
new 分配空间;引用
数据结构·c++
别NULL4 小时前
机试题——疯长的草
数据结构·c++·算法
CYBEREXP20085 小时前
MacOS M3源代码编译Qt6.8.1
c++·qt·macos
yuanbenshidiaos6 小时前
c++------------------函数
开发语言·c++
yuanbenshidiaos6 小时前
C++----------函数的调用机制
java·c++·算法
tianmu_sama6 小时前
[Effective C++]条款38-39 复合和private继承
开发语言·c++
羚羊角uou6 小时前
【C++】优先级队列以及仿函数
开发语言·c++
姚先生976 小时前
LeetCode 54. 螺旋矩阵 (C++实现)
c++·leetcode·矩阵
FeboReigns6 小时前
C++简明教程(文章要求学过一点C语言)(1)
c语言·开发语言·c++
FeboReigns6 小时前
C++简明教程(文章要求学过一点C语言)(2)
c语言·开发语言·c++