所有权模型
内容简介
所有权回答一个通用问题:谁负责释放、关闭或结束一个资源。资源可以是内存、文件句柄、网络连接、锁、线程、数据库连接,也可以是 UI 对象、协程任务或业务实体。
它不是 C++ 独有概念。C++ 只是把这个问题暴露得更直接:语言允许手动管理资源,所以所有权不清楚时,泄漏、重复释放、悬挂引用会很快出现。
为什么学 C++ 能帮助理解其他语言
很多语言都在用不同方式处理所有权:
| 语言/机制 | 设计思路 |
|---|---|
| C | 手动申请和释放,靠约定维护所有权 |
| C++ | RAII、值语义、智能指针,把所有权放进类型和对象生命周期 |
| Rust | 借用检查器在编译期检查所有权和借用 |
| Java / Go / C# | 垃圾回收管理内存,但文件、锁、连接仍然需要明确释放 |
| Python / JavaScript | 运行时管理对象生命周期,但外部资源仍需要上下文管理或显式关闭 |
所以所有权不是“指针问题”,而是资源生命周期问题。
常见关系
| 关系 | 含义 | C++ 常见表达 |
|---|---|---|
| 独占所有权 | 只有一个所有者负责释放资源 | std::unique_ptr<T> |
| 共享所有权 | 多个所有者共同延长资源生命周期 | std::shared_ptr<T> |
| 弱观察 | 观察共享资源但不延长生命周期 | std::weak_ptr<T> |
| 只观察 | 只使用,不负责释放 | T&、T* |
| 值拥有 | 对象自己保存数据 | 普通成员、std::string、std::vector |
基本原则
- 先问清楚:谁拥有资源,谁只是在使用资源。
- 能用值语义就用值语义。
- 能让对象自动释放资源,就不要让调用者记得手动释放。
- 共享所有权要谨慎,它通常比独占所有权更难推理。
- 只观察对象时,不要伪装成拥有对象。
C++ 示例
#include <memory>
struct Widget {};
std::unique_ptr<Widget> create_widget()
{
return std::make_unique<Widget>();
}
void use_widget(const Widget& widget)
{
// 只使用,不拥有
}返回 std::unique_ptr<Widget> 表示调用者拿到独占所有权;传 const Widget& 表示只观察,不负责释放。
易错点
Widget* create_widget()
{
return new Widget(); // 调用者是否负责 delete?接口不清楚
}这种接口容易泄漏,也容易重复释放。更清楚的写法是返回 std::unique_ptr<Widget>。
迁移到其他语言的理解
- 看到
with open(...)、defer file.Close()、try-with-resources,本质都是资源所有权和自动释放。 - 看到 Rust 的 move、borrow、lifetime,本质是在编译期强制表达所有权。
- 看到 Java/Go 的 GC,也要记住 GC 管内存,不代表所有资源都不用关闭。