在 C++ 中,动态内存管理是一个让人头疼的问题。忘记 delete 会导致内存泄漏,多次 delete 会导致程序崩溃。智能指针的出现,让内存管理变得自动化和安全。
1. 为什么需要智能指针?
1.1 手动内存管理的问题
cpp
void problematic() {
int* ptr = new int(42);
// 如果这里发生异常...
doSomething(); // 可能抛出异常
delete ptr; // 这行可能永远执行不到
}
常见问题:
- 内存泄漏 :忘记
delete - 悬垂指针 :
delete后继续使用 - 重复释放 :多次
delete同一个指针 - 异常安全 :异常导致
delete未执行
1.2 智能指针的解决方案
智能指针利用 RAII(Resource Acquisition Is Initialization)思想:
- 构造时获取资源(
new) - 析构时释放资源(
delete) - 离开作用域时自动调用析构函数
2. unique_ptr(独占指针)
2.1 基本特性
unique_ptr 表示独占所有权:
- 同一时刻只能有一个
unique_ptr指向同一个对象 - 不能复制,只能移动
- 适用于大多数场景的首选
2.2 基本用法
cpp
#include <iostream>
#include <memory>
using namespace std;
int main() {
// 创建 unique_ptr
unique_ptr<int> ptr1 = make_unique<int>(42);
// 访问值
cout << *ptr1 << endl; // 输出:42
// 检查是否为空
if (ptr1) {
cout << "ptr1 不为空" << endl;
}
// 获取原始指针(不推荐频繁使用)
int* raw = ptr1.get();
cout << *raw << endl; // 输出:42
// 离开作用域时自动释放内存
return 0;
}
2.3 管理数组
cpp
#include <iostream>
#include <memory>
using namespace std;
int main() {
// 管理动态数组
unique_ptr<int[]> arr = make_unique<int[]>(5);
for (int i = 0; i < 5; i++) {
arr[i] = i * 10;
}
for (int i = 0; i < 5; i++) {
cout << arr[i] << " "; // 输出:0 10 20 30 40
}
cout << endl;
// 自动调用 delete[]
return 0;
}
2.4 转移所有权
cpp
#include <iostream>
#include <memory>
using namespace std;
int main() {
unique_ptr<int> ptr1 = make_unique<int>(42);
// unique_ptr<int> ptr2 = ptr1; // 错误!不能复制
unique_ptr<int> ptr2 = move(ptr1); // 移动所有权
// ptr1 现在为空
if (!ptr1) {
cout << "ptr1 为空" << endl;
}
cout << *ptr2 << endl; // 输出:42
return 0;
}
2.5 作为函数参数和返回值
cpp
#include <iostream>
#include <memory>
using namespace std;
// 作为参数(转移所有权)
void takeOwnership(unique_ptr<int> ptr) {
cout << "接收到:" << *ptr << endl;
// 函数结束时自动释放
}
// 作为返回值
unique_ptr<int> createObject() {
return make_unique<int>(100);
}
// 引用传递(不转移所有权)
void useObject(const unique_ptr<int>& ptr) {
cout << "使用对象:" << *ptr << endl;
}
int main() {
auto ptr = make_unique<int>(42);
useObject(ptr); // 引用传递,ptr 仍然有效
takeOwnership(move(ptr)); // 移动所有权,ptr 变为空
auto newObj = createObject();
cout << *newObj << endl; // 输出:100
return 0;
}
2.6 自定义删除器
cpp
#include <iostream>
#include <memory>
#include <cstdio>
using namespace std;
int main() {
// 使用自定义删除器管理文件
unique_ptr<FILE, decltype(&fclose)> file(
fopen("test.txt", "w"),
fclose
);
if (file) {
fprintf(file.get(), "Hello, Smart Pointer!\n");
}
// 文件会在 unique_ptr 销毁时自动关闭
return 0;
}
3. shared_ptr(共享指针)
3.1 基本特性
shared_ptr 表示共享所有权:
- 多个
shared_ptr可以指向同一个对象 - 内部使用引用计数
- 当最后一个
shared_ptr销毁时,释放对象
3.2 基本用法
cpp
#include <iostream>
#include <memory>
using namespace std;
int main() {
// 创建 shared_ptr
shared_ptr<int> ptr1 = make_shared<int>(42);
cout << "引用计数:" << ptr1.use_count() << endl; // 输出:1
{
shared_ptr<int> ptr2 = ptr1; // 复制,引用计数+1
cout << "引用计数:" << ptr1.use_count() << endl; // 输出:2
shared_ptr<int> ptr3 = ptr1;
cout << "引用计数:" << ptr1.use_count() << endl; // 输出:3
} // ptr2 和 ptr3 销毁,引用计数-2
cout << "引用计数:" << ptr1.use_count() << endl; // 输出:1
cout << *ptr1 << endl; // 输出:42
return 0;
} // ptr1 销毁,对象释放
3.3 引用计数机制
cpp
#include <iostream>
#include <memory>
using namespace std;
class MyClass {
public:
MyClass(int v) : value(v) {
cout << "构造:" << value << endl;
}
~MyClass() {
cout << "析构:" << value << endl;
}
int value;
};
int main() {
cout << "=== 创建对象 ===" << endl;
shared_ptr<MyClass> ptr1 = make_shared<MyClass>(100);
cout << "\n=== 复制指针 ===" << endl;
shared_ptr<MyClass> ptr2 = ptr1;
shared_ptr<MyClass> ptr3 = ptr1;
cout << "引用计数:" << ptr1.use_count() << endl;
cout << "\n=== 重置指针 ===" << endl;
ptr2.reset(); // 引用计数-1
cout << "引用计数:" << ptr1.use_count() << endl;
cout << "\n=== 程序结束 ===" << endl;
return 0;
}
输出:
=== 创建对象 ===
构造:100
=== 复制指针 ===
引用计数:3
=== 重置指针 ===
引用计数:2
=== 程序结束 ===
析构:100
3.4 make_shared 的优势
cpp
// 方式一:分开写(两次内存分配)
shared_ptr<int> ptr1(new int(42));
// 方式二:make_shared(一次内存分配)
shared_ptr<int> ptr2 = make_shared<int>(42);
make_shared 的优势:
- 性能更好:一次内存分配(对象和控制块一起分配)
- 异常安全:避免中间状态的内存泄漏
- 更简洁:不需要写
new
3.5 shared_ptr 的陷阱
陷阱 1:循环引用
cpp
#include <iostream>
#include <memory>
using namespace std;
class B; // 前向声明
class A {
public:
shared_ptr<B> b_ptr;
~A() { cout << "A 析构" << endl; }
};
class B {
public:
shared_ptr<A> a_ptr; // 问题所在!
~B() { cout << "B 析构" << endl; }
};
int main() {
shared_ptr<A> a = make_shared<A>();
shared_ptr<B> b = make_shared<B>();
a->b_ptr = b;
b->a_ptr = a;
cout << "a 引用计数:" << a.use_count() << endl; // 输出:2
cout << "b 引用计数:" << b.use_count() << endl; // 输出:2
return 0;
// a 和 b 都不会被析构!内存泄漏
}
解决方案 :使用 weak_ptr(见下一节)
陷阱 2:从原始指针创建多个 shared_ptr
cpp
int* raw = new int(42);
shared_ptr<int> ptr1(raw);
shared_ptr<int> ptr2(raw); // 错误!会导致重复释放
陷阱 3:shared_ptr 管理数组(C++17 前)
cpp
// C++17 前需要自定义删除器
shared_ptr<int> arr(new int[10], default_delete<int[]>());
// C++17 后可以直接使用
shared_ptr<int[]> arr2(new int[10]);
4. weak_ptr(弱引用指针)
4.1 基本特性
weak_ptr 是 shared_ptr 的助手:
- 不增加引用计数
- 不拥有对象的所有权
- 用于解决循环引用问题
- 可以检测对象是否还存在
4.2 基本用法
cpp
#include <iostream>
#include <memory>
using namespace std;
int main() {
weak_ptr<int> weak;
{
shared_ptr<int> shared = make_shared<int>(42);
weak = shared; // weak 指向 shared 管理的对象
cout << "shared 引用计数:" << shared.use_count() << endl; // 输出:1
cout << "weak 是否过期:" << weak.expired() << endl; // 输出:0 (false)
// 通过 weak 获取 shared
if (auto locked = weak.lock()) {
cout << "值:" << *locked << endl; // 输出:42
cout << "shared 引用计数:" << locked.use_count() << endl; // 输出:2
}
} // shared 销毁,对象释放
cout << "weak 是否过期:" << weak.expired() << endl; // 输出:1 (true)
auto locked = weak.lock();
if (!locked) {
cout << "对象已不存在" << endl;
}
return 0;
}
4.3 解决循环引用
cpp
#include <iostream>
#include <memory>
using namespace std;
class B;
class A {
public:
shared_ptr<B> b_ptr;
~A() { cout << "A 析构" << endl; }
};
class B {
public:
weak_ptr<A> a_ptr; // 使用 weak_ptr 打破循环
~B() { cout << "B 析构" << endl; }
};
int main() {
shared_ptr<A> a = make_shared<A>();
shared_ptr<B> b = make_shared<B>();
a->b_ptr = b;
b->a_ptr = a;
cout << "a 引用计数:" << a.use_count() << endl; // 输出:1
cout << "b 引用计数:" << b.use_count() << endl; // 输出:2
return 0;
// a 和 b 都能正常析构
}
4.4 实际应用:缓存系统
cpp
#include <iostream>
#include <memory>
#include <unordered_map>
using namespace std;
class Data {
public:
string content;
Data(string c) : content(c) {
cout << "Data 构造:" << content << endl;
}
~Data() {
cout << "Data 析构:" << content << endl;
}
};
class Cache {
private:
unordered_map<string, weak_ptr<Data>> cache;
public:
shared_ptr<Data> get(const string& key) {
auto it = cache.find(key);
if (it != cache.end()) {
// 尝试获取 shared_ptr
if (auto ptr = it->second.lock()) {
cout << "缓存命中:" << key << endl;
return ptr;
} else {
// 对象已被释放,删除过期条目
cache.erase(it);
}
}
cout << "缓存未命中:" << key << endl;
return nullptr;
}
void put(const string& key, shared_ptr<Data> data) {
cache[key] = data;
}
};
int main() {
Cache cache;
{
auto data = make_shared<Data>("Hello");
cache.put("greeting", data);
auto retrieved = cache.get("greeting");
if (retrieved) {
cout << "内容:" << retrieved->content << endl;
}
} // data 销毁
auto retrieved = cache.get("greeting");
if (!retrieved) {
cout << "数据已被释放" << endl;
}
return 0;
}
5. 智能指针对比
| 特性 | unique_ptr | shared_ptr | weak_ptr |
|---|---|---|---|
| 所有权 | 独占 | 共享 | 无 |
| 复制 | ❌ | ✅ | ✅ |
| 移动 | ✅ | ✅ | - |
| 引用计数 | 无 | 有 | 不影响 |
| 内存开销 | 最小 | 较大 | 较小 |
| 适用场景 | 多数场景 | 共享所有权 | 打破循环引用 |
6. 最佳实践
6.1 优先使用 unique_ptr
cpp
// 好:明确的所有权
unique_ptr<Widget> createWidget() {
return make_unique<Widget>();
}
// 只在需要共享时使用 shared_ptr
shared_ptr<Widget> shared = make_shared<Widget>();
6.2 使用 make_unique 和 make_shared
cpp
// 好
auto ptr = make_unique<int>(42);
// 避免
unique_ptr<int> ptr(new int(42));
6.3 避免混用原始指针和智能指针
cpp
// 危险
int* raw = new int(42);
shared_ptr<int> ptr(raw);
delete raw; // 重复释放!
// 好
shared_ptr<int> ptr = make_shared<int>(42);
6.4 使用 weak_ptr 打破循环引用
当两个对象需要相互引用时,一方使用 weak_ptr。
7. 总结
智能指针是现代 C++ 内存管理的核心:
- unique_ptr:独占所有权,首选方案
- shared_ptr:共享所有权,引用计数
- weak_ptr:弱引用,解决循环引用
核心原则:
- 优先使用栈对象,必要时用智能指针
- 优先使用
unique_ptr - 使用
make_unique和make_shared - 避免混用原始指针和智能指针
下一节我们将学习多线程编程,智能指针在其中会发挥重要作用。