智能指针使接口中的所有权变得清晰可见。但它们不会取代仅用于借用对象的引用和原始指针;对于这类对象,其生命周期由其他位置保证。应根据生命周期契约选择指针类型,并在 API 边界明确表达所有权转移和对象观察。
唯一所有权 #
std::unique_ptr<T> 拥有一个对象,并且只能移动。普通 new 和默认删除器适用时,应使用 std::make_unique 创建;要转移所有权,应按值传递或按值返回。
#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 可以同时分配对象和控制块,从而减少分配次数。由于弱观察者会保留控制块,这种合并分配可能导致对象的存储空间一直无法释放,直到所有观察者消失。
别名构造函数在保存不同指针的同时共享已有的控制块:
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 契约 #
使用能够说明预期关系的签名:
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 上阅读或评论(发表评论需要登录)