在 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超出作用域或被显式删除时,其所管理的对象会被自动销毁。 - 使用示例:
#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; }
- 转移所有权:
auto anotherPtr = std::move(smartPtr); // 将所有权从 smartPtr 转移到 anotherPtr // 此时 smartPtr 已经不再拥有任何对象,anotherPtr 拥有原始对象
2. std::shared_ptr
- 作用:允许多个
std::shared_ptr同时指向同一个对象,并且只有当最后一个std::shared_ptr被销毁时,该对象才会被释放。 - 使用示例:
#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 打破这种循环引用是它最主要的用途。
struct B; struct A { std::shared_ptr<B> b_ptr; }; struct B { std::weak_ptr<A> a_ptr; // 用 weak_ptr 避免循环引用 };
2. 临时访问共享资源,不延长资源生命周期
有时你希望访问某个资源,但不希望因为你访问它就延长它的生命,这时候就用 weak_ptr。
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管理的资源,但不参与引用计数,也不会影响资源的释放时间。
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(不会增加引用计数):
std::shared_ptr<MyClass> sp = std::make_shared<MyClass>(); std::weak_ptr<MyClass> wp = sp;
- ❌ 不能直接从
weak_ptr获得裸指针或引用,需要先调用.lock()获取shared_ptr:
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。
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✅
{ std::unique_ptr<int> up = std::make_unique<int>(10); } // up 被销毁,对象立即释放
🔹 shared_ptr
- 拥有共享资源,离开作用域时:
- 智能指针对象本身析构 ✅
- 资源只有在所有
shared_ptr都销毁(引用计数 = 0)时才被释放 ⚠️
{ 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对象立刻析构 ✅- 它观察的资源不会受影响 ❌
{ 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 在何时被销毁(对象析构)。
我们定义一个简单的类,打印构造与析构,以便观察指针何时释放资源:
✅ 示例代码(含注释):
#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 validn"; } else { std::cout << "locked shared_ptr is nulln"; } } 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 resourcen"; // std::unique_ptr<MyClass> up2 = up1; // ❌ 错误:不能拷贝 std::unique_ptr<MyClass> up2 = std::move(up1); // ✅ 移动所有权 if (!up1) { std::cout << "up1 is now null after moven"; } if (up2) { std::cout << "up2 owns the resource after moven"; } // 用 reset() 释放当前对象 up2.reset(); // 调用析构函数,释放 MyClass(3) std::cout << "up2.reset() called, resource releasedn"; // 用 reset(new T) 替换为新的对象 up2.reset(new MyClass(4)); std::cout << "up2 now owns a new objectn"; // 离开作用域后,up2 自动释放新对象 MyClass(4) } int main() { demo_shared_ptr(); demo_weak_ptr(); demo_unique_ptr(); std::cout << "n[Program End]n"; return 0; }
🧾 输出示例(说明资源释放时机):
[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结合使用,用来解决循环引用问题,或者仅作为临时引用而不影响对象生命周期。