在 C++ 中,智能指针(smart pointers)是用于管理动态分配对象生命周期的类模板。它们旨在帮助开发者自动管理内存,避免常见的内存泄漏问题,并简化资源管理。C++ 标准库提供了三种主要类型的智能指针:std::unique_ptr
、std::shared_ptr
和 std::weak_ptr
。每种类型都有其特定的应用场景。
智能指针的作用
- 自动内存管理 :智能指针能够自动释放所指向的对象,从而避免了手动调用
delete
可能导致的内存泄漏。 - 所有权语义 :
std::unique_ptr
实现独占所有权(exclusive ownership),即一个对象只能由一个std::unique_ptr
所有。std::shared_ptr
支持共享所有权(shared ownership),允许多个std::shared_ptr
共同拥有同一个对象。std::weak_ptr
用于解决循环引用的问题,它提供了一种非拥有的引用方式来观察std::shared_ptr
管理的对象。
如何使用智能指针
1. std::unique_ptr
- 作用 :确保只有一个指针可以指向某个对象,当该
std::unique_ptr
超出作用域或被显式删除时,其所管理的对象会被自动销毁。 - 使用示例:
cpp
#include <iostream>
#include <memory> // 包含智能指针相关的头文件
int main() {
// 创建一个 unique_ptr 指向一个新的 int 对象
std::unique_ptr<int> smartPtr = std::make_unique<int>(10);
if (smartPtr) {
std::cout << "Value: " << *smartPtr << std::endl;
}
// 不需要手动调用 delete,离开作用域时自动释放
return 0;
}
- 转移所有权:
cpp
auto anotherPtr = std::move(smartPtr); // 将所有权从 smartPtr 转移到 anotherPtr
// 此时 smartPtr 已经不再拥有任何对象,anotherPtr 拥有原始对象
2. std::shared_ptr
- 作用 :允许多个
std::shared_ptr
同时指向同一个对象,并且只有当最后一个std::shared_ptr
被销毁时,该对象才会被释放。 - 使用示例:
cpp
#include <iostream>
#include <memory>
int main() {
std::shared_ptr<int> sp1 = std::make_shared<int>(20);
{
std::shared_ptr<int> sp2 = sp1; // 增加引用计数
std::cout << "sp1 use count: " << sp1.use_count() << std::endl; // 输出 2
} // sp2 超出作用域,引用计数减少
std::cout << "sp1 use count after sp2 is destroyed: " << sp1.use_count() << std::endl; // 输出 1
return 0;
}
3. std::weak_ptr
主要作用:
1. 解决循环引用(reference cycle)问题
当两个或多个 shared_ptr
相互引用时,会导致内存泄漏(因为引用计数永远不为0)。用 weak_ptr
打破这种循环引用是它最主要的用途。
cpp
struct B;
struct A {
std::shared_ptr<B> b_ptr;
};
struct B {
std::weak_ptr<A> a_ptr; // 用 weak_ptr 避免循环引用
};
2. 临时访问共享资源,不延长资源生命周期
有时你希望访问某个资源,但不希望因为你访问它就延长它的生命,这时候就用 weak_ptr
。
cpp
std::shared_ptr<int> sp = std::make_shared<int>(42);
std::weak_ptr<int> wp = sp; // 不增加引用计数
if (auto spt = wp.lock()) { // lock() 返回 shared_ptr,如果资源已释放则返回空指针
std::cout << *spt << '\n';
} else {
std::cout << "资源已经被释放\n";
}
weak_ptr
和 shared_ptr
关系
weak_ptr
和 shared_ptr
是 C++ 智能指针库中的两个紧密相关的类型,它们的关系可以从以下几个方面来理解:
weak_ptr
是对 shared_ptr
的一种"非拥有"引用
shared_ptr
管理资源的生命周期 ,并维护引用计数(use_count)。weak_ptr
观察由shared_ptr
管理的资源,但不参与引用计数,也不会影响资源的释放时间。
cpp
std::shared_ptr<int> sp = std::make_shared<int>(10);
std::weak_ptr<int> wp = sp; // wp 不增加引用计数
2. weak_ptr
可以从 shared_ptr
构造而来,反之不能直接构造
- ✅ 从
shared_ptr
创建weak_ptr
(不会增加引用计数):
cpp
std::shared_ptr<MyClass> sp = std::make_shared<MyClass>();
std::weak_ptr<MyClass> wp = sp;
- ❌ 不能直接从
weak_ptr
获得裸指针或引用,需要先调用.lock()
获取shared_ptr
:
cpp
if (auto spt = wp.lock()) {
// 使用 spt 访问对象
}
3. 共享相同的引用控制块(control block)
shared_ptr
和由它生成的weak_ptr
都共享同一个控制块。- 控制块中包含:
use_count
:当前有多少个shared_ptr
拥有资源。weak_count
:当前有多少个weak_ptr
引用控制块。
- 控制块中包含:
- 当
use_count == 0
时,资源被释放; - 当
use_count == 0 && weak_count == 0
时,控制块本身也被销毁。
4. 典型应用场景:解决循环引用
在两个类相互引用时,如果都用 shared_ptr
,就可能发生资源无法释放的问题(循环引用)。这时,应该让其中一方使用 weak_ptr
。
cpp
struct Node {
std::shared_ptr<Node> next;
std::weak_ptr<Node> prev; // 防止循环引用
};
总结一句话:
shared_ptr
是资源的"所有者",weak_ptr
是资源的"旁观者";weak_ptr
依赖shared_ptr
存在,不能单独使用。
如果你希望一个对象共享资源 → 用 shared_ptr
; 如果你希望访问但不拥有资源(避免循环引用或延长生命周期)→ 用 weak_ptr
。
使用 weak_ptr 的几个关键点:
特性 | 说明 |
---|---|
weak_ptr<T> |
不拥有资源,不增加引用计数 |
.lock() |
尝试获取资源的 shared_ptr ,可能为空 |
.expired() |
判断资源是否已经被销毁 |
.use_count() |
返回当前资源被多少个 shared_ptr 拥有 |
智能指针的生命周期
shared_ptr
、weak_ptr
和 unique_ptr
这些智能指针对象本身在离开作用域时会立刻被析构。 智能指针对象本身(shared_ptr
、weak_ptr
、unique_ptr
)在离开作用域时都会立刻析构;是否释放资源,取决于它是否拥有资源,以及资源的引用计数是否归零。
关键点总结:
智能指针类型 | 指针对象(自身)离开作用域是否立刻析构? | 资源是否一定被释放? |
---|---|---|
unique_ptr |
✅ 是,立刻析构 | ✅ 是,立即释放资源 |
shared_ptr |
✅ 是,立刻析构 | ⚠️ 仅当引用计数为 0 时释放资源 |
weak_ptr |
✅ 是,立刻析构 | ❌ 否,不影响资源是否释放(不拥有资源) |
🔹 unique_ptr
- 拥有唯一资源 ,离开作用域时:
- 智能指针对象本身被析构 ✅
- 所管理的资源也立即
delete
✅
cpp
{
std::unique_ptr<int> up = std::make_unique<int>(10);
} // up 被销毁,对象立即释放
🔹 shared_ptr
- 拥有共享资源 ,离开作用域时:
- 智能指针对象本身析构 ✅
- 资源只有在所有
shared_ptr
都销毁(引用计数 = 0)时才被释放 ⚠️
cpp
{
std::shared_ptr<int> sp1 = std::make_shared<int>(20);
{
std::shared_ptr<int> sp2 = sp1; // use_count = 2
} // sp2 析构,但资源还没释放
} // sp1 析构,use_count = 0,资源释放
🔹 weak_ptr
- 不拥有资源 ,离开作用域时:
weak_ptr
对象立刻析构 ✅- 它观察的资源不会受影响 ❌
cpp
{
std::weak_ptr<int> wp;
{
std::shared_ptr<int> sp = std::make_shared<int>(30);
wp = sp;
} // sp 析构,资源释放
} // wp 析构,但没影响资源释放时机
下面是一个完整的 C++ 示例程序,清晰演示了 shared_ptr
、weak_ptr
和 unique_ptr
在何时被销毁(对象析构)。
我们定义一个简单的类,打印构造与析构,以便观察指针何时释放资源:
✅ 示例代码(含注释):
cpp
#include <iostream>
#include <memory>
class MyClass {
public:
MyClass(int val) : value(val) {
std::cout << "MyClass(" << value << ") constructed.\n";
}
~MyClass() {
std::cout << "MyClass(" << value << ") destructed.\n";
}
private:
int value;
};
void demo_shared_ptr() {
std::cout << "\n[shared_ptr demo]\n";
std::shared_ptr<MyClass> sp1 = std::make_shared<MyClass>(1);
{
std::shared_ptr<MyClass> sp2 = sp1;
std::cout << "shared_ptr use_count: " << sp1.use_count() << "\n";
} // sp2 离开作用域,use_count 减 1
std::cout << "shared_ptr use_count: " << sp1.use_count() << "\n";
} // sp1 离开作用域,对象析构
void demo_weak_ptr() {
std::cout << "\n[weak_ptr demo]\n";
std::weak_ptr<MyClass> wp;
{
std::shared_ptr<MyClass> sp = std::make_shared<MyClass>(2);
wp = sp;
std::cout << "Inside scope, expired? " << std::boolalpha << wp.expired() << "\n";
} // sp 被销毁,资源被释放
std::cout << "Outside scope, expired? " << std::boolalpha << wp.expired() << "\n";
if (auto locked = wp.lock()) {
std::cout << "locked shared_ptr is valid\n";
} else {
std::cout << "locked shared_ptr is null\n";
}
}
void demo_unique_ptr() {
std::cout << "\n[unique_ptr demo]\n";;
std::unique_ptr<MyClass> up1 = std::make_unique<MyClass>(3);
std::cout << "up1 owns the resource\n";
// std::unique_ptr<MyClass> up2 = up1; // ❌ 错误:不能拷贝
std::unique_ptr<MyClass> up2 = std::move(up1); // ✅ 移动所有权
if (!up1) {
std::cout << "up1 is now null after move\n";
}
if (up2) {
std::cout << "up2 owns the resource after move\n";
}
// 用 reset() 释放当前对象
up2.reset(); // 调用析构函数,释放 MyClass(3)
std::cout << "up2.reset() called, resource released\n";
// 用 reset(new T) 替换为新的对象
up2.reset(new MyClass(4));
std::cout << "up2 now owns a new object\n";
// 离开作用域后,up2 自动释放新对象 MyClass(4)
}
int main() {
demo_shared_ptr();
demo_weak_ptr();
demo_unique_ptr();
std::cout << "\n[Program End]\n";
return 0;
}
🧾 输出示例(说明资源释放时机):
text
[shared_ptr demo]
MyClass(1) constructed.
shared_ptr use_count: 2
shared_ptr use_count: 1
MyClass(1) destructed.
[weak_ptr demo]
MyClass(2) constructed.
Inside scope, expired? false
MyClass(2) destructed.
Outside scope, expired? true
locked shared_ptr is null
[unique_ptr demo with move and reset]
MyClass(3) constructed.
up1 owns the resource
up1 is now null after move
up2 owns the resource after move
MyClass(3) destructed.
up2.reset() called, resource released
MyClass(4) constructed.
up2 now owns a new object
MyClass(4) destructed.
[Program End]
总结
std::unique_ptr
:适用于单一所有权的情况,是最轻量级的选择,性能最优。std::shared_ptr
:适用于需要多个指针共享同一资源的场景,但要注意潜在的性能开销和循环引用问题。std::weak_ptr
:通常与std::shared_ptr
结合使用,用来解决循环引用问题,或者仅作为临时引用而不影响对象生命周期。