✅ 本篇目标:
- 理解 RAII(资源获取即初始化)思想
- 掌握
unique_ptr和shared_ptr- 学会用智能指针管理动态对象
- 实战:用智能指针重构通讯录,支持"联系人详情"
- 初识 移动语义(Move Semantics)------ 高性能的秘密
🕒 建议学习时间:3--4 小时|可分多次完成💡 即使你从未手动
new/delete,也请认真学 ------ 这是现代 C++ 的基石!
📘 C++ 零基础入门教程(第 5 篇)
智能指针与 RAII ------ 让内存管理自动化
第一步:为什么需要智能指针?
❌ 传统 new/delete 的问题:
cpp
void riskyFunction() {
Student* s = new Student("Alice", 95); // 分配内存
// ... 中间可能抛出异常或提前 return
delete s; // 如果没执行到这行 → 内存泄漏!
}
🔥 内存泄漏:分配的内存未被释放,程序越跑越慢,最终崩溃。
✅ 解决方案:RAII(Resource Acquisition Is Initialization)
- 资源(如内存、文件句柄)由对象的生命周期管理
- 对象创建时获取资源,销毁时自动释放
- 智能指针 = 自动 delete 的指针
第二步:std::unique_ptr ------ 独占所有权
unique_ptr 表示 唯一拥有 某块内存,不能复制,但可转移。
2.1 基本用法
cpp
#include <iostream>
#include <memory> // 必须包含
using namespace std;
class Widget {
public:
Widget(string name) : name_(name) {
cout << "Widget " << name_ << " 创建\n";
}
~Widget() {
cout << "Widget " << name_ << " 销毁\n"; // 自动调用!
}
void doWork() { cout << name_ << " working...\n"; }
private:
string name_;
};
int main() {
// 创建 unique_ptr(推荐用 make_unique)
auto w = make_unique<Widget>("A");
w->doWork(); // 用 -> 调用成员函数
// 函数结束时,w 自动销毁,调用 ~Widget()
return 0;
}
✅ 输出:
Widget A 创建
A working...
Widget A 销毁
🔑 关键点:
make_unique<T>(args...)是创建unique_ptr的安全方式- 无需
delete!离开作用域自动释放- 不能复制:
auto w2 = w;❌ 编译错误- 可转移:
auto w2 = std::move(w);✅
2.2 何时使用 unique_ptr?
- 当你确定只有一个所有者时(最常见场景)
- 替代原始指针 +
new/delete - 用于类成员(确保对象销毁时资源释放)
第三步:std::shared_ptr ------ 共享所有权
shared_ptr 使用引用计数,多个指针共享同一对象,最后一个销毁时才释放内存。
示例:
cpp
#include <iostream>
#include <memory>
using namespace std;
int main() {
shared_ptr<Widget> p1 = make_shared<Widget>("B");
{
shared_ptr<Widget> p2 = p1; // 引用计数 = 2
cout << "p2 created, use_count=" << p1.use_count() << endl;
} // p2 离开作用域,引用计数 = 1
cout << "after p2 destroyed, use_count=" << p1.use_count() << endl;
return 0; // p1 销毁,引用计数=0 → 自动 delete
}
✅ 输出:
Widget B 创建
p2 created, use_count=2
after p2 destroyed, use_count=1
Widget B 销毁
⚠️ 注意:
- 用
make_shared<T>()创建(比make_unique更高效)- 避免循环引用(否则内存永不释放 → 用
weak_ptr解决,本篇不展开)
第四步:智能指针 vs 原始指针
| 场景 | 推荐 |
|---|---|
| 拥有资源(如 new 出的对象) | ✅ unique_ptr / shared_ptr |
| 仅观察/临时访问(不拥有) | ✅ 原始指针 或 引用 |
| 函数参数传递 | ✅ 优先用 const 引用 |
🎯 黄金法则 :
永远不要写new和delete!用
make_unique/make_shared代替。
第五步:实战项目 ------ 智能指针版通讯录(支持联系人详情)
我们将升级第 4 篇的通讯录:
- 每个联系人可附加"备注"(动态分配)
- 用
unique_ptr管理备注,避免泄漏
✅ 改造 Contact 类:
cpp
// contact_smart.cpp
#include <iostream>
#include <vector>
#include <string>
#include <memory> // for unique_ptr
using namespace std;
class Contact {
public:
string name;
string phone;
unique_ptr<string> note; // 备注(可选)
Contact(string n, string p) : name(n), phone(p) {}
void setNote(const string& n) {
note = make_unique<string>(n); // 自动管理内存
}
void print() const {
cout << name << "\t" << phone;
if (note) {
cout << "\t[备注: " << *note << "]";
}
cout << endl;
}
};
class ContactBook {
private:
vector<Contact> contacts;
public:
void addContact(const string& name, const string& phone) {
contacts.emplace_back(name, phone); // 直接构造,避免拷贝
cout << "已添加 " << name << endl;
}
void addNoteTo(const string& name, const string& noteText) {
for (auto& c : contacts) {
if (c.name == name) {
c.setNote(noteText);
cout << "已为 " << name << " 添加备注" << endl;
return;
}
}
cout << "未找到 " << name << endl;
}
void listAll() const {
if (contacts.empty()) {
cout << "通讯录为空" << endl;
return;
}
cout << "\n--- 通讯录 ---\n";
for (const auto& c : contacts) {
c.print();
}
}
};
int main() {
ContactBook book;
book.addContact("Alice", "13800138000");
book.addContact("Bob", "13900139000");
book.addNoteTo("Alice", "同事,前端工程师");
book.listAll();
return 0;
}
✅ 输出:
已添加 Alice
已添加 Bob
已为 Alice 添加备注
--- 通讯录 ---
Alice 13800138000 [备注: 同事,前端工程师]
Bob 13900139000
🎉 亮点:
note是unique_ptr<string>,即使不设备注也不会浪费内存- 对象销毁时,
note自动释放,零泄漏风险- 使用
emplace_back直接构造 Contact,避免临时对象拷贝
第六步:初识移动语义(Move Semantics)
问题:vector.push_back(大对象) 会拷贝,很慢!
✅ 解决方案:移动(Move)而非拷贝
- 把资源"转移"给新对象,原对象变空
- 适用于
unique_ptr、vector、string等
示例:
cpp
vector<string> words;
string big = "非常长的字符串...";
words.push_back(big); // 拷贝(慢)
words.push_back(move(big)); // 移动(快!big 变为空)
💡 在你的代码中:
emplace_back(args...)比push_back(T(args))更高效- 返回局部对象时,编译器自动移动(无需
move)
📌 本篇小结:你已掌握
| 概念 | 说明 |
|---|---|
| RAII | 资源由对象生命周期管理 |
unique_ptr |
独占所有权,自动 delete,不可复制 |
shared_ptr |
共享所有权,引用计数,最后销毁 |
make_unique / make_shared |
安全创建智能指针 |
| 移动语义 | 避免拷贝,提升性能 |
| 现代 C++ 原则 | 不写 new/delete,用 STL + 智能指针 |
✅ 下一步建议
- 尝试 :将成绩系统中的
Student改为用unique_ptr存储(虽然没必要,但可练习) - 思考 :如果两个 Contact 互相引用对方(循环),
shared_ptr会泄漏吗?如何解决? - 预习:什么是"模板"?如何写出通用算法?