栈与堆
局部对象在栈上,作用域结束自动析构,快且安全;new 在堆上分配,需手动 delete,慢且易泄漏。优先栈上对象。
| 维度 | 栈 | 堆 |
|---|---|---|
| 分配速度 | 极快(移动栈指针) | 较慢(需查找空闲块) |
| 生命周期 | 作用域结束自动释放 | 手动 delete 或智能指针 |
| 大小 | 有限 | 大 |
new 与 delete
裸 new 必须配对 delete(数组用 delete[])。现代 C++ 几乎不该手写 new/delete——交给智能指针或容器。
int* p = new int(42);
// ... 使用 p
delete p; // 必须,且只一次
p = nullptr; // 置空,避免悬垂指针忘记 delete 泄漏;重复 delete 未定义行为;异常路径跳过 delete 泄漏。智能指针一次性解决这三者。
unique_ptr
std::unique_ptr 表达独占所有权:同一时刻只有一个指针拥有对象,不可拷贝,可移动。零额外开销,是默认首选。
auto p = std::make_unique<Widget>(42); // 推荐 make_unique
// 离开作用域自动 delete
std::unique_ptr<Widget> q = std::move(p); // 转移所有权make_unique 更简洁、异常安全(避免 new 与构造间的内存泄漏窗口)。
shared_ptr
std::shared_ptr 表达共享所有权:用引用计数,最后一个持有者析构时才释放。开销高于 unique_ptr,仅在确需共享时用。
auto a = std::make_shared<Widget>();
auto b = a; // 引用计数 +1
// a、b 都离开作用域后对象才释放两个 shared_ptr 互相持有会形成引用计数永远不为 0 的环,导致泄漏。环的一侧改用 weak_ptr 打破。
weak_ptr
std::weak_ptr 是 shared_ptr 的非拥有观察者:不增加引用计数,用 lock() 临时升级为 shared_ptr 使用。
std::weak_ptr<Widget> w = a; // 不增加计数
if (auto s = w.lock()) { // 提升成功说明对象还在
s->use();
}所有权原则
每个资源都有唯一"负责人":函数参数用 const T& 或 shared_ptr<const T> 表示"借用/共享",返回值用 unique_ptr 表示"移交所有权"。
// 工厂返回独占所有权
std::unique_ptr<Widget> create() {
return std::make_unique<Widget>();
}泄漏检测
用 AddressSanitizer(编译加 -fsanitize=address)或 valgrind 检测内存错误与泄漏,纳入 CI 自动把关。
g++ -std=c++20 -fsanitize=address -g main.cpp -o main
./main # 运行时报告泄漏/越界ASan 性能好、定位准、能抓越界与 use-after-free;valgrind 更慢但无需重编译,二者互补。
常见陷阱与 FAQ
可以,用 std::vector<std::unique_ptr<T>>,元素间所有权独立,移动语义保证安全。这是管理多态对象集合的惯用法。
需要把"自身"作为 shared_ptr 传出去时,让类继承 std::enable_shared_from_this,用 shared_from_this(),避免重复构造控制块。
unique_ptr 与裸指针开销几乎一致(编译期信息);shared_ptr 有原子引用计数开销,但远小于手写管理的隐性 bug 成本。