条款 18 - 使用 std::unique_ptr 管理具备专属所有权的资源
进入现代C++, 传统的指针由于容易出现资源泄露等缺点,
智能指针成为了管理资源的重要工具. C++11 中共有四种智能指针:std::auto
_ptr, std::unique_ptr, std::shared_ptr 和 std:
:weak_ptr。所有这些智能指针都是为管理动态分配对象的生命期而设计的,其中,
std::unique_ptr 是一种独占式智能指针,
它确保在任何时候只有一个指针指向资源. 这使得
std::unique_ptr
成为了管理具备专属所有权的资源的理想选择.
而且在一般情况下, std::unique_ptr 应该是智能指针的默认首选, 它在效率上也几乎与裸指针无异
std::auto_ptr 是从 C++98 中残留下来的弃用特性, 现在已经基本被std::unique_ptr 所替代.
专属所有权与移动语义
std::unique_ptr 体现了“专属所有权”的概念,即一个非空的 std::unique_ptr 总是拥有其所指向的资源 。任何时刻,资源都只有一个所有者。这确保了资源的安全释放,避免了内存泄漏和悬空指针的问题。
轻量且高效:在默认情况下(即使用 delete 作为删除器),std::unique_ptr 的尺寸与裸指针完全相同,并且其操作(如解引用)的执行效率也与裸指针相同 。这使得它即使在对性能和内存要求极为苛刻的场景下也同样适用 。
移动专属 (Move-Only):std::unique_ptr 不允许复制, 如果你尝试复制一个 std::unique_ptr,代码将无法通过编译。这是为了保证所有权的唯一性。因此要转移资源的所有权,只能且必须使用 std::move 。当一个 std::unique_ptr 被移动后,源指针将被置为 nullptr 。
自动资源管理:当一个 std::unique_ptr 被销毁时(例如离开作用域),它会自动销毁其所拥有的资源 。默认情况下,它通过在其内部的裸指针上调用 delete 来完成这一操作 。
典型用例:工厂函数
std::unique_ptr 最常见的用法之一,是作为工厂函数的返回类型 。工厂函数通常在堆上创建一个对象并返回一个指向它的指针,而调用者则负责该对象的生命周期, 这与 std::unique_ptr 的专属所有权语义完美契合。工厂函数通过返回一个 std::unique_ptr,清晰地将新创建的对象的所有权转移给调用方 。
1 | // 假设 Investment 是一个多态基类 |
自定义删除器
std::unique_ptr 不仅限于使用 delete 来释放资源,它还支持自定义删除器 (custom deleter) 。删除器可以是任意函数或函数对象(包括 lambda 表达式),用于在 std::unique_ptr 销毁时执行特定的清理操作。自定义删除器的类型是 std::unique_ptr 模板的第二个参数 。
1 | // 自定义删除器,在删除前先写入日志 |
函数指针删除器:如果删除器是一个函数指针,std::unique_ptr 的尺寸通常会从一个字长(裸指针的大小)增加到两个字长(一个用于裸指针,一个用于函数指针)。
函数对象删除器(包括 lambda):如果删除器是一个函数对象,其尺寸取决于该函数对象中存储了多少状态。如果是一个无捕获的 lambda,它不包含任何状态,编译器通常可以将其完全优化掉,此时 std::unique_ptr 的尺寸不会增加,仍然和一个裸指针一样大 。
因此,当需要自定义删除器时,使用无捕获的 lambda 是比使用函数指针更高效的选择 。
std::unique_ptr 的两种形式:单个对象与数组
std::unique_ptr 提供两种形式:一种用于单个对象,一种用于数组 。
std::unique_ptr
:用于单个对象。它提供了 operator* 和 operator->。 std::unique_ptr<T[]>:用于数组。它提供了 operator[] 索引运算符,但不提供 * 和 -> 。
不过, 在现代 C++ 中,std::array、std::vector 和 std::string 等标准容器几乎总是比裸数组更好的选择,因此 std::unique_ptr<T[]> 的使用场景非常有限 。
转换为 std::shared_ptr
std::unique_ptr 的一个非常吸引人的特性是,它可以方便且高效地转换为 std::shared_ptr。
并且通常来说, std::unique_ptr 是构建 std::shared_ptr 的完美来源。这使得 std::unique_ptr 成为工厂函数的理想返回类型。工厂函数返回一个高效的 std::unique_ptr,将专属所有权交给调用者。如果调用者后续需要共享这个资源,他们可以自行决定将其转换为 std::shared_ptr,从而将所有权模型从专属升级为共享 。