智能指针是 C++ 中用于自动管理动态内存 的工具,核心思想是利用栈对象的生命周期管理(RAII),在智能指针对象离开作用域时自动释放所管理的堆内存,从而避免内存泄漏。
C++11 标准库提供了三种主要的智能指针,它们定义在 头文件中:
| 智能指针类型 | 头文件 | 核心特点 | 所有权模型 |
|---|---|---|---|
std::unique_ptr |
<memory> |
独占对象所有权,不可复制,只能移动 | 独占所有权 |
std::shared_ptr |
<memory> |
共享对象所有权,通过引用计数管理 | 共享所有权 |
std::weak_ptr |
<memory> |
辅助型,不控制对象生命周期,可解决循环引用问题 | 无所有权 |
一、 std::unique_ptr ------ 独占所有权
unique_ptr 代表对一个对象的独占所有权。这意味着在任何时候,只有一个 unique_ptr 可以指向同一个对象。当这个 unique_ptr 被销毁(离开作用域、被重置或被移动)时,它所指向的对象会被自动释放。
核心特性
- 不可复制:禁用了拷贝构造函数和拷贝赋值运算符。
- 可移动:可以通过 std::move 将所有权从一个 unique_ptr 转移到另一个。
- 高效:几乎没有额外开销,大小通常与裸指针相同。
代码示例
cpp
#include <iostream>
#include <memory> // 包含智能指针头文件
class Device {
public:
std::string name;
Device(const std::string& n) : name(n) {
std::cout << "Device " << name << " 已创建\n";
}
~Device() {
std::cout << "Device " << name << " 已销毁\n";
}
void run() {
std::cout << "Device " << name << " 正在运行...\n";
}
};
int main() {
// 1. 创建 unique_ptr,管理一个 Device 对象
std::unique_ptr<Device> dev1 = std::make_unique<Device>("CNC_01");
// 使用 -> 访问对象成员
dev1->run();
// 2. 尝试复制(编译错误)
// std::unique_ptr<Device> dev2 = dev1; // 错误:unique_ptr 不可复制
// 3. 移动所有权(正确)
std::unique_ptr<Device> dev2 = std::move(dev1);
if (!dev1) {
std::cout << "dev1 现在为空,所有权已转移给 dev2\n";
}
dev2->run();
// 4. 手动释放资源(可选,通常不需要)
dev2.reset(); // 此时 Device 对象被销毁
if (!dev2) {
std::cout << "dev2 现在为空\n";
}
// 注意:当 dev1 和 dev2 离开 main 作用域时,如果它们还持有对象,会自动释放
return 0;
}
输出:
plaintext
Device CNC_01 已创建
Device CNC_01 正在运行...
dev1 现在为空,所有权已转移给 dev2
Device CNC_01 正在运行...
Device CNC_01 已销毁
dev2 现在为空
二、 std::shared_ptr ------ 共享所有权
shared_ptr 允许多个指针共享同一个对象的所有权 。它内部维护一个线程安全的引用计数。当最后一个指向该对象的 shared_ptr 被销毁时,对象才会被释放。
核心特性
- 可复制:拷贝时引用计数加1。
- 可移动:移动时引用计数不变。
- 线程安全:引用计数的增减是原子操作。
代码示例
cpp
#include <iostream>
#include <memory>
#include <vector>
int main() {
// 1. 创建 shared_ptr,管理一个 Device 对象
std::shared_ptr<Device> dev1 = std::make_shared<Device>("Robot_Arm");
std::cout << "当前引用计数: " << dev1.use_count() << "\n"; // 输出 1
// 2. 复制 shared_ptr,引用计数增加
std::shared_ptr<Device> dev2 = dev1;
std::cout << "当前引用计数: " << dev1.use_count() << "\n"; // 输出 2
// 3. 将 shared_ptr 放入容器
std::vector<std::shared_ptr<Device>> deviceList;
deviceList.push_back(dev1);
deviceList.push_back(dev2);
std::cout << "当前引用计数: " << dev1.use_count() << "\n"; // 输出 4
// 4. 销毁一个 shared_ptr,引用计数减少
dev1.reset();
std::cout << "当前引用计数: " << dev2.use_count() << "\n"; // 输出 3
// 5. 清空容器,引用计数继续减少
deviceList.clear();
std::cout << "当前引用计数: " << dev2.use_count() << "\n"; // 输出 1
// 6. 当最后一个 shared_ptr (dev2) 离开作用域时,对象被销毁
return 0;
}
输出:
plaintext
Device Robot_Arm 已创建
当前引用计数: 1
当前引用计数: 2
当前引用计数: 4
当前引用计数: 3
当前引用计数: 1
Device Robot_Arm 已销毁
三、 std::weak_ptr ------ 解决循环引用
weak_ptr 是为了配合 shared_ptr 而设计的。它不拥有对象的所有权 ,因此不会增加或减少引用计数。它的主要作用是观察由 shared_ptr 管理的对象,而不影响其生命周期,从而解决 shared_ptr 的循环引用问题。
循环引用问题
当两个对象互相持有对方的 shared_ptr 时,它们的引用计数永远不会降为0,导致内存泄漏。
cpp
// ❌ 错误示例:循环引用导致内存泄漏
class Node {
public:
std::shared_ptr<Node> next;
~Node() { std::cout << "Node 已销毁\n"; }
};
int main() {
auto node1 = std::make_shared<Node>();
auto node2 = std::make_shared<Node>();
node1->next = node2; // node1 持有 node2
node2->next = node1; // node2 持有 node1
// 离开作用域时,两个 Node 对象都不会被销毁!
}
使用 weak_ptr 解决问题
cpp
#include <iostream>
#include <memory>
// ✅ 正确示例:使用 weak_ptr 打破循环
class Node {
public:
std::weak_ptr<Node> next; // 使用 weak_ptr 替代 shared_ptr
~Node() { std::cout << "Node 已销毁\n"; }
};
int main() {
auto node1 = std::make_shared<Node>();
auto node2 = std::make_shared<Node>();
node1->next = node2; // weak_ptr 不增加引用计数
node2->next = node1;
// 离开作用域时,node1 和 node2 被销毁,它们管理的 Node 对象也被销毁
return 0;
}
输出:
plaintext
Node 已销毁
Node 已销毁
如何使用 weak_ptr
由于 weak_ptr 不拥有对象,不能直接使用 * 或 -> 操作符。必须先将其提升为 shared_ptr ,并检查对象是否还存在。
cpp
int main() {
auto shared = std::make_shared<Device>("Sensor");
std::weak_ptr<Device> weak = shared;
// 提升为 shared_ptr 以访问对象
if (auto locked = weak.lock()) {
locked->run(); // 安全访问
std::cout << "对象仍然存在\n";
} else {
std::cout << "对象已被销毁\n";
}
shared.reset(); // 销毁对象
// 再次尝试提升
if (auto locked = weak.lock()) {
locked->run();
} else {
std::cout << "对象已被销毁,无法访问\n";
}
return 0;
}
四、最佳实践与总结
1. 优先使用 std::make_unique 和 std::make_shared
- 它们能保证异常安全,避免内存泄漏。
- make_shared 还能优化内存分配,将控制块和对象内存分配在一起。
cpp
// 推荐
auto dev = std::make_unique<Device>("Machine");
auto dev = std::make_shared<Device>("Machine");
// 不推荐(可能导致内存泄漏)
Device* raw = new Device("Machine");
std::unique_ptr<Device> dev(raw);
2. 避免裸指针与智能指针混用
- 不要将同一个裸指针交给多个智能指针管理,这会导致重复释放。
- 一旦对象交给智能指针管理,就不要再使用裸指针操作它。
3. 选择合适的智能指针
- unique_ptr :当你需要独占对象所有权,且性能要求高时。它是默认选择。
- shared_ptr :当你需要多个所有者共享对象时。注意其引用计数的开销。
- weak_ptr :专门用于解决 shared_ptr 的循环引用,或在不拥有所有权的情况下观察对象。
五、 手写 unique_ptr 核心实现
cpp
#include <iostream>
using namespace std;
template <typename T>
class MyUniquePtr {
private:
T* _ptr;
// 禁用拷贝构造和拷贝赋值
MyUniquePtr(const MyUniquePtr&) = delete;
MyUniquePtr& operator=(const MyUniquePtr&) = delete;
public:
// 构造
explicit MyUniquePtr(T* p = nullptr) : _ptr(p) {}
// 移动构造
MyUniquePtr(MyUniquePtr&& other) noexcept {
_ptr = other._ptr;
other._ptr = nullptr;
}
// 移动赋值
MyUniquePtr& operator=(MyUniquePtr&& other) noexcept {
if (this != &other) {
delete _ptr;
_ptr = other._ptr;
other._ptr = nullptr;
}
return *this;
}
// 析构
~MyUniquePtr() {
delete _ptr;
_ptr = nullptr;
}
// 解引用
T& operator*() const { return *_ptr; }
T* operator->() const { return _ptr; }
T* get() const { return _ptr; }
};
// 测试
struct Test {
Test() { cout << "Test 构造\n"; }
~Test() { cout << "Test 析构\n"; }
};
int main() {
MyUniquePtr<Test> p1(new Test);
MyUniquePtr<Test> p2 = move(p1); // 转移所有权
return 0;
}
核心原理:
- 禁止拷贝,只允许移动
- 析构时直接 delete
- 无引用计数,效率最高
六、 手写 shared_ptr 核心实现(带引用计数)
cpp
#include <iostream>
using namespace std;
template <typename T>
class MySharedPtr {
private:
T* _ptr;
int* _refCount; // 引用计数必须在堆上,共享
void release() {
if (_ptr) {
if (--(*_refCount) == 0) {
delete _ptr;
delete _refCount;
_ptr = nullptr;
_refCount = nullptr;
}
}
}
public:
// 构造
explicit MySharedPtr(T* p = nullptr) : _ptr(p) {
_refCount = new int(1);
}
// 拷贝构造:计数+1
MySharedPtr(const MySharedPtr& other) {
_ptr = other._ptr;
_refCount = other._refCount;
(*_refCount)++;
}
// 拷贝赋值
MySharedPtr& operator=(const MySharedPtr& other) {
if (this != &other) {
release(); // 释放当前资源
_ptr = other._ptr;
_refCount = other._refCount;
(*_refCount)++;
}
return *this;
}
// 析构
~MySharedPtr() {
release();
}
int use_count() const { return *_refCount; }
T* get() const { return _ptr; }
T& operator*() const { return *_ptr; }
T* operator->() const { return _ptr; }
};
// 测试
struct Test {
Test() { cout << "Test 构造\n"; }
~Test() { cout << "Test 析构\n"; }
};
int main() {
MySharedPtr<Test> p1(new Test);
MySharedPtr<Test> p2 = p1;
cout << "count: " << p1.use_count() << endl; // 2
return 0;
}
核心原理:
- 堆上维护引用计数
- 拷贝 = 计数+1
- 析构/赋值 = 计数-1
- 计数为0才真正释放对象
七、 手写 weak_ptr 核心实现(解决循环引用)
weak_ptr 必须和 shared_ptr 配合,不增加强引用计数。
cpp
#include <iostream>
using namespace std;
// 先声明
template <typename T> class MySharedPtr;
template <typename T>
class MyWeakPtr {
private:
T* _ptr;
int* _refCount;
int* _weakCount;
public:
// 从 shared_ptr 构造
MyWeakPtr(const MySharedPtr<T>& other) {
_ptr = other._ptr;
_refCount = other._refCount;
_weakCount = other._weakCount;
(*_weakCount)++;
}
~MyWeakPtr() {
(*_weakCount)--;
}
// 提升为 shared_ptr
MySharedPtr<T> lock() const {
if (*_refCount > 0)
return MySharedPtr<T>(*this);
return MySharedPtr<T>();
}
bool expired() const { return *_refCount == 0; }
};
// 完整版 MySharedPtr(支持 weak_ptr)
template <typename T>
class MySharedPtr {
private:
T* _ptr;
int* _refCount; // 强引用
int* _weakCount; // 弱引用
friend class MyWeakPtr<T>;
void release() {
if (_ptr) {
if (--(*_refCount) == 0) {
delete _ptr;
_ptr = nullptr;
// 弱计数为0才释放控制块
if (*_weakCount == 0) {
delete _refCount;
delete _weakCount;
}
}
}
}
public:
explicit MySharedPtr(T* p = nullptr) : _ptr(p) {
_refCount = new int(1);
_weakCount = new int(0);
}
MySharedPtr(const MyWeakPtr<T>& wp) {
_ptr = wp._ptr;
_refCount = wp._refCount;
_weakCount = wp._weakCount;
(*_refCount)++;
}
MySharedPtr(const MySharedPtr& other) {
ptr = other._ptr;
_refCount = other._refCount;
_weakCount = other._weakCount;
(*_refCount)++;
}
MySharedPtr& operator=(const MySharedPtr& other) {
if (this != &other) {
release();
_ptr = other._ptr;
_refCount = other._refCount;
_weakCount = other._weakCount;
(*_refCount)++;
}
return *this;
}
~MySharedPtr() {
release();
}
int use_count() const { return *_refCount; }
};
// 测试循环引用
struct A;
struct B;
struct A {
MyWeakPtr<B> b;
A() { cout << "A 构造\n"; }
~A() { cout << "A 析构\n"; }
};
struct B {
MySharedPtr<A> a;
B() { cout << "B 构造\n"; }
~B() { cout << "B 析构\n"; }
};
int main() {
MySharedPtr<A> a(new A);
MySharedPtr<B> b(new B);
a->b = b;
b->a = a;
return 0;
}
核心原理:
- 只增加弱计数,不增加强计数
- 不影响对象生命周期
- 用 lock() 安全提升为 shared_ptr
- 解决循环引用内存泄漏
极简总结(背这个)
- unique_ptr:禁止拷贝,独占所有权,直接delete。
- shared_ptr:堆上引用计数,拷贝+1,析构-1,为0释放。
- weak_ptr:观察者,不增加强计数,解决循环引用。