86 / 103 · C++11 · 约 12 分钟
值复制与共享所有权:同样的复制,不同的承诺
复制一个对象究竟复制数据,还是复制访问同一数据的入口,必须由类型接口讲清楚。对照 vector 的独立值与 shared_ptr 的共享对象,并用 weak_ptr 验证观察者不会延长资源寿命。
C++ Primer
已按连续区间阅读第1—19章全部可提取正文、代码、习题、术语及附录A与索引,不是只读样章。附录A.1图像表已补读PDF1057—1063。研究副本仍有仅以图像存在的表格、图示与代码未逐一恢复,部分代码转录截断;因此不声称原版所有图文完整通读。练习未执行。
查看版本、实际阅读范围与原文入口 →先决定复制在业务上是什么意思
书中动态内存与复制控制紧密相连:语言能逐成员复制,却不能替你判断成员代表独立数据还是共享责任。vector 的复制得到独立序列,修改副本不改变原序列;shared_ptr 的复制则增加同一对象的拥有者,不会自动复制所指对象。
两者都是真正的复制,只是复制层次不同。设计接口时应先回答“调用者拿到副本以后是否还能影响我”,再选择表示。不能因为智能指针负责释放,就默认它同时提供值语义;也不应为了避免手工析构,把原本独占或直接存储的数据无条件改成共享。
观察者需要承认对象可能消失
weak_ptr 保存的是可检测的观察关系,不计入强拥有者数量。每次真正使用前调用 lock,得到一个局部 shared_ptr;只要这个局部拥有者还活着,使用期间对象就不会因其他拥有者退出而消失。lock 失败时,接口必须定义缺失的处理方式。
示例先在内部作用域复制强拥有者,确认两者访问同一个 vector;退出后对象仍由外层变量拥有。最后一个强拥有者离开时,观察者变为过期。这比记住一个具体 use_count 数字更接近业务契约,也避免把调试计数误当成线程同步方案。
选择最小的所有权机制
值对象优先直接保存值;确有单一转移责任时才考虑 unique_ptr;多个独立主体真的需要共同维持寿命时再使用 shared_ptr。weak_ptr 解决观察和部分环形所有权问题,不会自动发现或拆除所有强引用环。
本例使用 C++11 已有的设施,C++20 仍适用。没有演示并发:控制块支持不同智能指针实例的某些并发操作,不代表被指向的 vector 自动线程安全。书中的手写容器与分配器用于理解机制,旧版本 allocator 成员和部分复制消除解释需按当前标准重新判断,不能原样当现代库实现。
容易答错的地方
- shared_ptr 的复制不深复制所指对象;两个接口可能在修改同一数据。
- weak_ptr::lock 得到的局部强引用会暂时延长寿命;expired 的检查不能替代随后使用时的 lock。
运行一个例子
最低标准 C++11 · 完整程序 · 下载 .cpp
#include <cassert>
#include <memory>
#include <vector>
int main() {
std::vector<int> original{1, 2};
auto independent = original;
independent[0] = 9;
assert(original[0] == 1 && independent[0] == 9);
std::weak_ptr<std::vector<int>> observer;
{
auto owner = std::make_shared<std::vector<int>>(original);
observer = owner;
{
auto shared = owner;
(*shared)[0] = 7;
assert((*owner)[0] == 7);
}
auto pinned = observer.lock();
assert(pinned && (*pinned)[0] == 7);
}
assert(observer.expired());
assert(!observer.lock());
}
在本地编译
g++ -std=c++11 -Wall -Wextra -Wpedantic -pthread books-cpp-primer.cpp -o example && ./example预期结果
预期:正常退出、无输出;所有 assert 通过。
CHECK YOUR UNDERSTANDING
合上答案,试着解释。
如果希望 shared 的修改不影响 owner,应该怎样建立副本?
查看参考答案
把 auto shared = owner 改为 auto shared = std::make_shared<std::vector<int>>(*owner)。这先复制 vector 值,再创建独立的拥有关系。修改 (*shared)[0] 后,(*owner)[0] 应保持1;不要从 owner.get() 构造另一个 shared_ptr,那会产生独立控制块并重复删除同一对象。
继续查证
标准草案链接会随工作草案更新;本文版本标记对应示例最低要求,不表示草案中的所有新规则都适用于旧标准。