1. 背景
智能指针不是指针,是一个管理指针的类,用来存储指向动态分配对象的指针,负责自动释放动态分配的对象,防止堆内存泄漏和悬空指针等问题。
动态分配的资源,交给一个类对象去管理,当类对象声明周期结束时,自动调用析构函数释放资源。
- C++ 98中产生第一个智能指针
auto_ptr
- C++ 11起提供三个主要的智能指针,位于
<memory>
头文件中:std::unique_ptr
std::shared_ptr
std::weak_ptr
关键特性对比
类型 | 所有权 | 复制语义 | 线程安全 | 性能开销 |
---|---|---|---|---|
unique_ptr |
独占 | 移动转移 | 单线程安全 | 无 |
shared_ptr |
共享 | 允许复制 | 引用计数原子化 | 较高 |
weak_ptr |
无所有权 | 允许复制 | 依赖关联shared | 低 |
2. 原理与使用
智能指针的基本原理是利用RAII,在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在 对象析构的时候释放资源(不需要显式地释放资源)。
如果只是简单地用类封装指针,如:
template<class T>
class Smartptr
{
public:
Smartptr(T* ptr) : _ptr(ptr) {}
~Smartptr() {
delete _ptr;
}
T& operator*() {
return *_ptr;
}
T* operator->(){
return _ptr;
}
private:
T* _ptr;
};
会出现拷贝问题:用一个智能指针赋值给另一个指针指针时,因为是浅拷贝,会将两个指针指向同一块内存,在程序结束析构智能指针时释放两次空间,导致程序崩溃。
为了解决这个问题,出现以下四类智能指针:
2.1 auto_ptr
原理:
管理权转移,被拷贝对象把资源管理权转移给拷贝对象,导致被拷贝对象悬空。保证了一个资源只有一个对象对其进行管理,这时候一个资源就不会被多个释放。
使用:
auto_ptr<int> ap1(new int(1));
auto_ptr<int> ap2(ap1);
// 此时ap1悬空
模拟实现:
template<class T>
class auto_ptr
{
public:
auto_ptr(T* ptr) : _ptr(ptr) {}
auto_ptr(const auto_ptr<T>& ap) {
_ptr = ap._ptr;
ap._ptr = nullptr;
}
auto_ptr<T>& operator=(const auto_ptr<T>& ap) {
// 检测是否自己给自己赋值
if(this != &ap) {
// 释放当前资源
if(_ptr) delete _ptr;
_ptr = ap._ptr;
ap._ptr = nullptr;
}
return *this;
}
~auto_ptr() {
delete _ptr;
}
T& operator*() {
return *_ptr;
}
T* operator->() {
return _ptr;
}
private:
T* _ptr;
};
2.2 unique_ptr
原理:
通过删除拷贝构造函数/赋值运算符来防止拷贝
使用:
unique_ptr<int> up0 = make_unique<int>(0);
unique_ptr<int> up1(new int(1));
unique_ptr<int> up2(new int(2));
sp1 = sp2; // 报错
在函数返回unique_ptr
时不要返回其引用:
auto getUnique() {
auto ptr = std::make_unique<int>(10);
return ptr; // 正确:移动语义转移所有权
}
// auto& getUniqueRef() { ... } // 错误:返回引用会导致悬空指针
模拟实现:
template<class T>
class unique_ptr
{
public:
unique_ptr(T* ptr) : _ptr(ptr) {}
unique_ptr(const unique_ptr<T>& up) = delete;
unique_ptr<T>& operator=(const unique_ptr<T>& ap) = delete;
~unique_ptr() {
delete _ptr;
}
T& operator*() {
return *_ptr;
}
T* operator->() {
return _ptr;
}
private:
T* _ptr;
};
2.3 shared_ptr
原理:
通过引用计数的方式解决智能指针的拷贝问题:
shared_ptr
给每个资源都维护了着一份计数用来记录该份资源被几个对象共享;- 在对象被销毁时(也就是析构函数调用),引用计数减一;
- 如果引用计数是0,就说明自己是最后一个使用该资源的对象,就可以释放该资源;
- 如果引用计数不是0,就说明还有其他对象在使用该份资源,不能释放该资源。
使用:
shared_ptr<int> sp1(new int(1));
cout << sp1.use_count() << endl; // 1
shared_ptr<int> sp2(sp1);
cout << sp2.use_count() << endl; // 2
注意:避免混用裸指针与智能指针
int* raw = new int(5);
std::shared_ptr<int> p1(raw);
std::shared_ptr<int> p2(raw); // 双重释放!
模拟实现:
template<class T>
class shared_ptr
{
public:
shared_ptr(T* ptr) : _ptr(ptr), _pcount(new int(1)) {}
~shared_ptr(){
Release();
}
shared_ptr(const shared_ptr<T>& sp){
_ptr = sp._ptr;
_pcount = sp._pcount;
++(*_pcount);
}
void Release() {
if (--(*_pcount) == 0) {
delete _pcount;
delete _ptr;
}
}
shared_ptr<T>& operator=(const shared_ptr<T>& sp) {
//资源地址不一样
if (_ptr != sp._ptr) {
Release();
_pcount = sp._pcount;
_ptr = sp._ptr;
++(*_pcount);
}
return *this;
}
int use_count() {
return *_pcount;
}
// 像指针一样
T& operator*() {
return *_ptr;
}
T* operator->() {
return _ptr;
}
T& operator[](size_t pos) {
return _ptr[pos];
}
private:
T* _ptr;
int* _pcount;
};
为什么引用计数要用指针,而不用成员变量或者静态成员变量?
若将引用计数作为普通成员变量:不同
shared_ptr
副本之间无法共享计数(成员变量属于对象,而非资源)。静态变量(
static
)的特性:- 属于类本身,所有对象共享同一个静态变量。
- 生命周期与程序一致,无法动态创建或销毁。
- 全局唯一性,无法为每个资源单独维护计数。
这与智能指针的资源独立性要求直接冲突:每个
shared_ptr
需要为其管理的资源单独维护引用计数,而静态变量会导致所有资源共享同一个计数器,引发严重错误。如果使用静态变量来计数,以下代码会出现错误:
int main() { int* a = new int(10); int* b = new int(20); BadSharedPtr p1(a); // count=1 BadSharedPtr p2(b); // count=2(错误!两个资源共享同一个计数器) p1.~BadSharedPtr(); // count=1(但 a 未被删除) p2.~BadSharedPtr(); // count=0,错误地删除 b,而 a 泄漏! return 0; } // 结果:a 内存泄漏,b 被提前释放,且程序崩溃。
循环引用问题:
class Node;
class Parent {
public:
std::shared_ptr<Node> child; // Parent 持有 Node 的 shared_ptr
~Parent() { std::cout << "Parent destroyed\n"; }
};
class Node {
public:
std::shared_ptr<Parent> parent; // Node 也持有 Parent 的 shared_ptr(循环引用)
~Node() { std::cout << "Node destroyed\n"; }
};
int main() {
auto parent = std::make_shared<Parent>(); // parent 引用计数 = 1
auto node = std::make_shared<Node>(); // node 引用计数 = 1
parent->child = node; // node 引用计数 +1 → 2
node->parent = parent; // parent 引用计数 +1 → 2
return 0;
// main 结束时:
// parent 引用计数 -1 → 1 → Parent 未被销毁!
// node 引用计数 -1 → 1 → Node 未被销毁!
}
2.4 weak_ptr
原理:
观察但不拥有资源,用于解决shared_ptr
循环引用问题。
使用:
解决
shared_ptr
循环引用问题:#include <memory> class Node; // 前置声明 class Parent { public: std::shared_ptr<Node> child; ~Parent() { std::cout << "Parent destroyed\n"; } }; class Node { public: // std::shared_ptr<Parent> parent; // 循环引用导致内存泄漏 std::weak_ptr<Parent> parent; // 改用 weak_ptr 解决 ~Node() { std::cout << "Node destroyed\n"; } }; int main() { auto parent = std::make_shared<Parent>(); // parent 引用计数 = 1 auto node = std::make_shared<Node>(); // node 引用计数 = 1 parent->child = node; // node 引用计数 +1 → 2 node->parent = parent; // weak_ptr 不会增加引用计数 return 0; // 正确析构 // main 结束时: // parent 引用计数 -1 → 0 → Parent 销毁 → child 销毁 → node 引用计数 -1 → 1 // node 引用计数 -1 → 0 → Node 销毁! }
安全访问共享资源:
#include <memory> #include <iostream> class Data { public: void process() { std::cout << "Processing data...\n"; } }; int main() { std::shared_ptr<Data> sharedData = std::make_shared<Data>(); std::weak_ptr<Data> weakData = sharedData; // 检查 weak_ptr 是否有效 if (auto tmp = weakData.lock()) { // 提升为 shared_ptr tmp->process(); // 安全使用 std::cout << "Use count: " << tmp.use_count() << "\n"; // 输出 2 } else { std::cout << "Data expired\n"; } sharedData.reset(); // 释放资源 if (weakData.expired()) { std::cout << "Data is no longer available\n"; } return 0; }
模拟实现:
template<class T>
class weak_ptr
{
public:
weak_ptr() :_ptr(nullptr) {}
weak_ptr(const shared_ptr<T>& wp) {
_ptr = wp.get();
}
weak_ptr<T>& operator=(const shared_ptr<T>& wp) {
_ptr = wp.get();
return *this;
}
T& operator*() {
return *_ptr;
}
T* operator->() {
return _ptr;
}
private:
T* _ptr;
};
2.5 定制删除器
定制删除器可以解决:如何正确释放用new
或者new []
开辟的资源。
template<class U, class D> unique_ptr(U* p, D del);
其中Del
参数是一个定制删除器,是一个可调用对象,比如函数指针、仿函数、lambda表达式以及被包装器包装后的可调用对象。
// unique_ptr 自定义删除器
auto del = [](int* p) { delete[] p; };
std::unique_ptr<int[], decltype(del)> arr(new int[10], del);
参考:
- C++智能指针详解-CSDN博客
- DeepSeek