C++ 智能指针与所有权

最后更新: 2024-02-28 10:22:21 +0800 +0800
原文以英文撰写。您正在阅读AI翻译版本。

智能指针使接口中的所有权变得清晰可见。但它们不会取代仅用于借用对象的引用和原始指针;对于这类对象,其生命周期由其他位置保证。应根据生命周期契约选择指针类型,并在 API 边界明确表达所有权转移和对象观察。

唯一所有权 #

std::unique_ptr<T> 拥有一个对象,并且只能移动。普通 new 和默认删除器适用时,应使用 std::make_unique 创建;要转移所有权,应按值传递或按值返回。

cpp
#include <memory>

std::unique_ptr<Connection> open_connection(Address address);
void install_connection(std::unique_ptr<Connection> connection);

install_connection(open_connection(address));

自定义删除器是 unique_ptr 类型的一部分,因此可能影响其大小以及 API 接受的类型。删除器必须满足所有权协议,并且不得在析构过程中抛出异常。

即使 T 是不完整类型,unique_ptr<T> 也可以出现在头文件中。但是,默认删除器要求在实例化销毁对象的操作处,T 已经是完整类型;这通常是拥有该对象的类的类外析构函数。unique_ptr<T[]> 表示数组分配,但不会保存数组长度;对于拥有的序列,应优先使用 std::vector<T>。

共享所有权与观察 #

std::shared_ptr<T> 共享一个控制块,其中包含所有权状态,通常还包含删除器和分配器。复制 shared_ptr 会延长所有权;最后一个拥有对象的指针会销毁被管理对象。std::make_shared 可以同时分配对象和控制块,从而减少分配次数。由于弱观察者会保留控制块,这种合并分配可能导致对象的存储空间一直无法释放,直到所有观察者消失。

别名构造函数在保存不同指针的同时共享已有的控制块:

cpp
auto owner = std::make_shared<Record>();
std::shared_ptr<Member> member(owner, &owner->member);

member.get() 指向子对象,而其生命周期由 owner 所拥有的 Record 控制。不要从同一个原始指针构造彼此无关的 shared_ptr 实例;每个实例都会认为自己独立拥有该对象。

std::weak_ptr<T> 观察控制块,但不会延长对象的生命周期。解引用前,应使用 lock() 获取临时的拥有指针。当所有权环会使对象图无限期存活时,应使用 weak_ptr 对反向边和缓存进行建模。

API 契约 #

使用能够说明预期关系的签名:

cpp
void take(std::unique_ptr<Job>);             // 转移唯一所有权
void share(std::shared_ptr<const Config>);    // 保持共享所有权
void inspect(const Config&);                  // 借用;非空
void maybe_inspect(const Config*);            // 借用;可为空

仅当操作既不需要保留所有权,也不需要公开所有权契约时,才接受 const std::shared_ptr<T>&;接受 T& 或 T* 能更直接地记录借用关系。借用的指针或引用仅在调用方承诺的生命周期内有效。

评论

在 GitHub 上阅读或评论(发表评论需要登录)