智能指针unique_ptr<>创建的过程

智能指针unique_ptr<>创建的过程

两种初始化方式的比较

std::unique_ptr 可以通过两种方式进行初始化:直接构造或者使用 std::make_unique()。它们之间的区别如下:

直接构造 std::unique_ptr

你可以通过直接构造来创建一个 unique_ptr,如下:

std::unique_ptr<int> ptr(new int(42));
  • 优点
    • 你可以在构造时精确控制对象的构造方式,比如分配自定义的内存管理器。
  • 缺点
    • 这种方式需要手动调用 new,可能会导致意外的内存泄漏。如果 new 分配成功但在接下来分配 std::unique_ptr 之前抛出异常,内存可能会泄漏。
    • 手动调用 new 是一种旧的习惯用法,不符合现代 C++ 的推荐实践。

使用 std::make_unique()

从 C++14 开始,标准库引入了 std::make_unique(),这是现代 C++ 推荐的创建 unique_ptr 的方式:

auto ptr = std::make_unique<int>(42);
  • 优点
    • 安全性make_unique 更加安全,因为它在单一表达式中创建对象并将其所有权交给 unique_ptr,不会出现手动调用 new 时的内存泄漏问题。
    • 简洁性:代码更简洁,不需要显式调用 new,减少手动管理内存的风险。
    • 异常安全性:在 make_unique 内部,内存分配和 unique_ptr 的创建是原子操作。如果在创建过程中发生异常,内存会被自动清理,避免了内存泄漏。
  • 缺点
    • 在需要自定义的删除器或内存管理策略时,make_unique 不如直接使用 unique_ptr 构造函数灵活。

第一种方式的内部细节

  1. 调用 new int(42)

    • new int(42) 会在堆上分配一个 int 对象,并初始化其值为 42。这一步返回一个 原生指针,指向堆上的新对象。
    • 假设这个原生指针为 p_raw,此时 p_raw 是一个指向整数的裸指针。
     
    int* p_raw = new int(42); // p_raw 是一个原生指针,指向堆上的整数对象

    第一步的结果

    • 产生了一个原生指针 p_raw,它指向堆上分配的 int(42) 对象。
  2. 创建 std::unique_ptr<int> ptr

    • 接下来,std::unique_ptr<int> 的构造函数被调用,接受该原生指针作为参数,并将其托管给 unique_ptr
    • std::unique_ptr 接管了该原生指针的所有权,此时 原生指针 已经不再被使用,所有权转移到了 std::unique_ptr 上。
     
    std::unique_ptr<int> ptr(p_raw); // ptr 接管了 p_raw 的所有权

    第二步的结果

    • std::unique_ptr 接管了该指针的所有权,并在其生命周期内管理这个堆上的 int 对象。该指针变成了 unique_ptr 内部持有的指针。
  3. 销毁原生指针

    • 在执行完 std::unique_ptr<int> ptr(new int(42)); 这行代码后,堆上对象的所有权只归 unique_ptr,裸指针 p_raw 不再存在(除非你显式声明了它)。

过程总结:

  • 两个指针

    • 第一步:当调用 new int(42) 时,产生了一个 原生指针,它指向新分配的 int 对象。
    • 第二步:std::unique_ptr 的构造函数接受这个原生指针,并接管其所有权,之后该原生指针不再使用。
  • 最终结果

    • 最终,只有一个指针(即 std::unique_ptr 内部持有的指针)管理这个堆上的对象。原生指针在 unique_ptr 接管后不再需要使用。

 

posted @ 2024-09-13 09:29  Gold_stein  阅读(111)  评论(0)    收藏  举报