大纲
C++ 智能指针
直接内存管理
直接内存管理的示例代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
| #include <iostream> #include <string> #include <vector>
using namespace std;
class MyClass { public: int m_i; };
void func() { int a = 0; MyClass mc; static int b = 30; }
void func2() { int* pint = new int; delete pint;
int* pint2 = new int(); delete pint2;
int* pint3 = new int(10); delete pint3;
string* pstr = new string; delete pstr;
string* pstr2 = new string(); delete pstr2;
string* pstr3 = new string(5, 'A');
auto pstr4 = new auto(pstr3); delete pstr3; delete pstr4;
vector<int>* pvec = new vector<int>{1, 2, 3}; delete pvec;
MyClass* pmc = new MyClass; delete pmc;
MyClass* pmc2 = new MyClass(); delete pmc2;
const int* pconst = new const int(100); delete pconst; }
|
同一块内存不能 delete 多次
- 不能
delete 多次的根本原因是:- 设计假设:内存管理器假设每次
delete 的都是正在使用中的内存 - 数据结构破坏:第二次
delete 会修改已被修改过的元数据,破坏空闲链表(Free List) - 性能取舍:为了性能,不维护复杂的 “是否已释放” 检测机制
- 不可确定性:第一次
delete 的内存可能已被重新分配,导致第二次 delete 释放的是其他对象的内存
delete 操作 | 结果 |
|---|
delete p; delete p; | ❌ 未定义行为(通常崩溃) |
delete p; p=nullptr; delete p; | ✅ 安全(delete nullptr 无效果) |
两个指针指向同一内存,多次 delete | ❌ 未定义行为 |
delete nullptr 多次 | ✅ 安全 |
1 2 3 4
| int* p = new int(10); delete p; p = nullptr; delete p;
|
1 2 3 4
| int* p1 = new int(10); int* p2 = p1; delete p1; delete p2;
|
1 2 3 4 5 6 7 8 9 10
| void badFunction(int* ptr) { delete ptr; }
int main() { int* p = new int(42); badFunction(p); delete p; return 0; }
|
1 2 3
| int* p = nullptr; delete p; delete p;
|
delete 操作的最佳实践总结
1 2 3 4
| unique_ptr<int> p1 = make_unique<int>(42); shared_ptr<int> p2 = make_shared<int>(42);
|
1 2 3 4 5 6 7 8 9
| class MyClass { int* data; public: MyClass() : data(new int(42)) {} ~MyClass() { delete data; } };
|
最佳实践总结
- 每个
new 只对应一个 delete,delete 后立即将指针置为 nullptr,更好的是方案使用智能指针自动管理内存。
智能指针的使用
智能指针的类型
在 C++ 中,智能指针的类型有以下几种:
- (1) 带引用计数的智能指针:
shared_ptr - (2) 不带引用计数的智能指针:
auto_ptr、scoped_ptr、unique_ptr - (3) 特殊的智能指针:
weak_ptr(不增加引用计数,可以用于避免 shared_ptr 发生循环引用)
| 智能指针 | C++ 标准 | 所有权 | 带引用计数 | 适用场景 | 核心特性 |
|---|
auto_ptr | C++ 98 | 独占(拷贝时转移) | 否 | ⚠ 已废弃,建议改用 unique_ptr | 独占所有权,在复制或赋值时会转移所有权,导致原指针变为空(nullptr) |
scoped_ptr | Boost | 独占 | 否 | 生命周期受限于作用域,适用于简单的场景,避免资源泄漏 | 独占所有权,不可复制或赋值,不支持移动语义,即不可以使用 std::move() 函数转移所有权 |
unique_ptr | C++ 11 | 独占 | 否 | 资源独占,生命周期明确 | 独占所有权,不可复制(拷贝构造和赋值),但可以移动(移动构造和移动赋值),即支持使用 std::move() 函数转移所有权 |
shared_ptr | C++ 11 | 共享 | 是 | 资源共享,生命周期不固定 | 共享所有权(允许多个智能指针管理同一个资源) |
weak_ptr | C++ 11 | 观察 shared_ptr | 否 | 避免 shared_ptr 发生循环引用 | 不增加引用计数,用于避免 shared_ptr 发生循环引用,可以通过 lock() 函数转换为 shared_ptr |
shared_ptr 智能指针
shared_ptr 的使用语法
正确的使用语法
shared_ptr 的正确用法- 第一种正确用法
shared_ptr<int> sp(new int(100));shared_ptr<MyClass> sp(new MyClass());
- 第二种正确用法
shared_ptr<int> sp = make_shared<int>(100);shared_ptr<MyClass> sp = make_shared<MyClass>();
- 第三种正确用法
shared_ptr<int> sp;sp 是指向 int 的智能指针,但目前指向的内存地址为空,即属于空指针
错误的使用语法
shared_ptr 的错误用法- 第一种错误用法(代码编译失败)
shared_ptr<int> sp = new int(100);- 智能指针是
explicit,不可以进行隐式类型转换,必须用直接初始化形式
- 第二种错误用法(代码可以正常运行,但不推荐使用)
int *pi = new int(100); shared_ptr<int> spi(pi);- 裸指针与智能指针尽量不要混用,否则会影响代码的健壮性
shared_ptr 的引用计数
特别注意
在 C++ 中,shared_ptr 的引用计数操作是线程安全的(使用原子操作),但 shared_ptr 指向的对象本身不是线程安全的,需要额外同步处理。
引用计数增加
shared_ptr 引用计数增加的说明- 移动操作:移动构造 / 移动赋值不会增加原对象的引用计数,只是转移所有权
1 2 3
| shared_ptr<int> p1 = std::make_shared<int>(10); shared_ptr<int> p2 = std::move(p1); p1 = std::move(p2);
|
shared_ptr 引用计数增加的情况- (1) 拷贝构造
1 2 3
| shared_ptr<int> p1 = std::make_shared<int>(42); shared_ptr<int> p2(p1); shared_ptr<int> p3 = p1;
|
- (2) 拷贝赋值
1 2 3 4
| shared_ptr<int> p1 = std::make_shared<int>(10); shared_ptr<int> p2 = std::make_shared<int>(20); p2 = p1;
|
- (3) 从
weak_ptr 构造 shared_ptr1 2 3 4 5 6 7
| shared_ptr<int> sp = std::make_shared<int>(5); std::weak_ptr<int> wp = sp;
shared_ptr<int> sp2 = wp.lock(); if (sp2) { }
|
- (4) 智能指针作为函数的形参(按值传递)
1 2 3 4 5 6 7
| void func(shared_ptr<int> sp) {
}
shared_ptr<int> sp = make_shared<int>(100); func(sp);
|
- (5) 智能指针作为函数的返回值
1 2 3 4 5 6 7
| std::shared_ptr<int> create() { shared_ptr<int> sp = std::make_shared<int>(50); return sp; }
shared_ptr<int> sp = create();
|
- (6) 容器操作(拷贝元素)
1 2 3 4
| std::vector<std::shared_ptr<int>> vec; shared_ptr<int> sp = std::make_shared<int>(10); vec.push_back(sp); vec.insert(vec.begin(), sp);
|
引用计数减少
shared_ptr 引用计数减少的说明- 当引用计数减少到 0 时,
shared_ptr 会:- (1) 调用删除器释放管理的对象
- (2) 释放控制块(如果没有
weak_ptr 指向它)
- 移动操作:移动构造 / 移动赋值不会减少原对象的引用计数,只是转移所有权
1 2
| shared_ptr<int> p1 = std::make_shared<int>(42); shared_ptr<int> p2 = std::move(p1);
|
weak_ptr 不会影响引用计数,但 weak_ptr 可以延长控制块的生存时间1 2 3 4
| shared_ptr<int> sp = std::make_shared<int>(10); std::weak_ptr<int> wp = sp; sp.reset();
|
shared_ptr 引用计数减少的情况- (1) 智能指针对象被销毁
1 2 3 4
| void func() { shared_ptr<int> p = std::make_shared<int>(42); }
|
- (2) 智能指针指向新的对象
1 2 3
| shared_ptr<int> sp = make_shared<int>(100); shared_ptr<int> sp2 = sp; sp = make_shared<int>(200);
|
- (3) 智能指针的
reset() 方法被调用1 2 3
| shared_ptr<int> p = std::make_shared<int>(10); p.reset(); p.reset(new int(20));
|
- (4) 容器中移除或替换元素
1 2 3 4 5
| std::vector<std::shared_ptr<int>> vec; vec.push_back(std::make_shared<int>(1)); vec.push_back(std::make_shared<int>(2)); vec.erase(vec.begin()); vec[0] = std::make_shared<int>(3);
|
shared_ptr 的常用操作
use_count()
use_count():返回有多少个智能指针指向某个对象,主要用于代码调试目的
1 2 3 4 5 6 7 8 9 10 11 12
| shared_ptr<int> sp = make_shared<int>(100); int count1 = sp.use_count(); cout << count1 << endl;
shared_ptr<int> sp2(sp); int count2 = sp.use_count(); cout << count2 << endl;
shared_ptr<int> sp3; sp3 = sp; int count3 = sp.use_count(); cout << count3 << endl;
|
unique()
unique():判断该智能指针是否独占某个指向的对象,也就是如果只有一个智能指针指向某个对象,那么 unique() 会返回 1,否则返回 0
1 2 3 4 5
| shared_ptr<int> sp = make_shared<int>(100); cout << sp.unique() << endl;
shared_ptr<int> sp2(sp); cout << sp.unique() << endl;
|
reset()
1 2 3 4
| shared_ptr<int> sp = make_shared<int>(100); shared_ptr<int> sp2 = sp; sp.reset(); sp2.reset();
|
1 2 3 4 5 6 7 8 9 10
| auto p1 = std::make_shared<int>(100); auto p2 = p1;
p1.reset(new int(200));
auto p3 = std::make_shared<int>(300);
p3.reset(new int(400));
|
1 2
| shared_ptr<int> sp; sp.reset(new int(100));
|
* 解引用
- 通过
* 获取 shared_ptr 所指向的对象
1 2
| shared_ptr<int> sp = make_shared<int>(100); cout << *sp << endl;
|
get()
get():返回 shared_ptr 存储的裸指针(通过 new 得到的原始指针)shared_ptr 返回的裸指针,千万不要手动 delete,否则会导致程序运行崩溃(未定义行为)- 注意:如果
shared_ptr 释放了所指向对象的内存,那么 get() 返回的裸指针也会变得无效
1 2 3 4
| shared_ptr<int> sp = make_shared<int>(100); int *p = sp.get(); *p = 150; cout << *p << endl;
|
swap()
swap():用于交换两个 shared_ptr 所指向的对象
1 2 3 4 5 6
| shared_ptr<int> sp = make_shared<int>(100); shared_ptr<int> sp2 = make_shared<int>(300); sp.swap(sp2);
cout << *sp << endl; cout << *sp2 << endl;
|
= nullptr
= nullptr- 唯一指向时:若当前
shared_ptr 是唯一指向所管理对象的智能指针(即引用计数为 1),则释放原对象的内存,然后将当前 shared_ptr 置为空指针 - 非唯一指向时:若有多个
shared_ptr 共享同一个对象(引用计数 > 1),则不释放原对象的内存,仅将原对象的引用计数减 1,然后将当前 shared_ptr 置为空指针 - 无论是哪种情况,执行
= nullptr 后,当前 shared_ptr 都会变为空指针(nullptr)
1 2 3 4
| shared_ptr<int> sp = make_shared<int>(100); shared_ptr<int> sp2 = sp; sp = nullptr; sp2 = nullptr;
|
shared_ptr 的删除器
自定义删除器
1 2 3 4 5 6 7 8
| void myDeleter(int *p) { delete p; p = nullptr; }
shared_ptr<int> sp(new int(100), myDeleter); sp.reset();
|
1 2 3 4 5 6
| shared_ptr<int> sp(new int(100), [](int *p) { delete p; p = nullptr; cout << "delete int *" << endl; });
|
make_shared 不支持自定义删除器的原因
- 设计目的:
make_shared 是为了一次分配内存(控制块和对象内存在一起)和异常安全,而自定义删除器通常用于管理非 new 分配的资源,比如文件句柄、自定义释放逻辑(动态数组)等 - 类型推导问题:删除器的类型会改变
shared_ptr 的类型签名,而 make_shared 无法推断这种类型信息 - 语义清晰:需要自定义删除器说明资源不是普通方式分配的,此时使用
new 显式创建对象更符合意图
使用数组的问题
如果 shared_ptr 指向的是数组,那么就需要自定义删除器或者特殊语法,否则 C++ 编译器不会使用 delete [] 正确释放数组的内存,而是默认使用 delete 导致内存泄漏
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| class MyClass { public: MyClass() { } ~MyClass() { cout << "~MyClass()" << endl; } };
int main() { shared_ptr<MyClass> sp(new MyClass[3], [](MyClass *p) { delete[] p; cout << "delete MyClass []" << endl; }); }
|
程序运行输出的结果如下:
1 2 3 4
| ~MyClass() ~MyClass() ~MyClass() delete MyClass []
|
除了上面通过函数或者 Lambda 表达式作为自定义删除器来释放数组内存,还可以使用 std::default_delete 来做删除器,std::default_delete 是标准库(STL)里的模板类,同样可以正确释放数组的内存
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| class MyClass { public: MyClass() { } ~MyClass() { cout << "~MyClass()" << endl; } };
int main() { shared_ptr<MyClass> sp(new MyClass[3], default_delete<MyClass[]>()); }
|
程序运行输出的结果如下:
1 2 3
| ~MyClass() ~MyClass() ~MyClass()
|
如果实在不想通过自定义删除器或者 std::default_delete 来正确释放数组的内存,还可以使用数组特化版本 shared_ptr<T[]>,同样可以正确释放数组内存(从 C++ 17 开始支持以下写法)
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| class MyClass { public: MyClass() { } ~MyClass() { cout << "~MyClass()" << endl; } };
int main() { shared_ptr<MyClass[]> sp2(new MyClass[3]); }
|
程序运行输出的结果如下:
1 2 3
| ~MyClass() ~MyClass() ~MyClass()
|
同样的,还可以封装一个函数模板来正确释放数组内存
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| class MyClass { public: MyClass() { } ~MyClass() { cout << "~MyClass()" << endl; } };
template <typename T> shared_ptr<T> make_shared_array(size_t size) { return shared_ptr<T>(new T[size], default_delete<T[]>()); }
int main() { shared_ptr<MyClass> sp = make_shared_array<MyClass>(3); }
|
程序运行输出的结果如下:
1 2 3
| ~MyClass() ~MyClass() ~MyClass()
|
同一种类型的说明
特别注意
如果两个 shared_ptr 指定了不同的删除器,只要它们所指向的对象类型是相同的,那么这两个 shared_ptr 也属于同一种类型。这就代表只要类型相同,就可以将它们放入到元素类型为该对象类型的容器里面。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| int main() { auto lambda1 = [](int *p) { delete p; cout << "delete by lambda1" << endl; };
auto lambda2 = [](int *p) { delete p; cout << "delete by lambda2" << endl; };
shared_ptr<int> p1(new int(100), lambda1);
shared_ptr<int> p2(new int(200), lambda2);
p2 = p1; }
|
程序运行输出的结果如下:
1 2
| delete by lambda2 delete by lambda1
|
shared_ptr 的使用陷阱
裸指针与智能指针混合使用
将一个裸指针绑定到一个 shared_ptr 之后,那内存管理的责任就应该交给 shared_ptr 了,这个时候就不能够再通过裸指针来访问 shared_ptr 所指向的内存。简而言之,在 C++ 中应该尽量避免同时使用裸指针和智能指针(如 shared_ptr)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| #include <iostream> #include <memory>
using namespace std;
void proc(shared_ptr<int> sp) { return; }
int main() { int *p = new int(100); proc(shared_ptr<int>(p)); *p = 45; return 0; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| #include <iostream> #include <memory>
using namespace std;
void proc(shared_ptr<int> sp) { return; }
int main() { shared_ptr<int> sp = make_shared<int>(100); proc(sp); *sp = 45; return 0; }
|
使用裸指针初始化多个智能指针
C++ 禁止使用同一个裸指针初始化多个独立的 shared_ptr,核心原因是避免重复释放内存。每个 shared_ptr 都有自己的控制块,如果从同一个裸指针分别创建,它们会互不知情,导致析构时对同一块内存释放两次,造成未定义行为。正确的做法是先创建一个 shared_ptr,然后用它拷贝构造其他 shared_ptr,这样它们才能共享同一个控制块和引用计数。
1 2 3 4 5 6 7 8 9 10 11 12
| #include <iostream> #include <memory>
using namespace std;
int main() { int * p = new int(100); shared_ptr<int> sp1(p); shared_ptr<int> sp2(p); return 0; }
|
1 2 3 4 5 6 7 8 9 10
| #include <iostream> #include <memory>
using namespace std;
int main() { shared_ptr<int> sp1 = make_shared<int>(100); shared_ptr<int> sp2(sp1); return 0; }
|
谨慎使用 get () 返回的裸指针
在 C++ 中,需要谨慎使用 shared_ptr 的 get() 返回的裸指针,是因为这个裸指针不参与引用计数管理。如果你 delete 这个裸指针,会导致原始内存被释放,但 shared_ptr 仍认为它拥有这块内存,最终在析构时会再次尝试释放,造成重复释放内存的未定义行为。此外,如果用这个裸指针去初始化另一个 shared_ptr,也会造成独立的控制块,同样导致重复释放内存。get() 返回的裸指针仅适合用于那些不转移所有权、不延长生命周期的只读访问场景。
1 2 3 4 5 6 7 8 9 10 11
| #include <iostream> #include <memory>
using namespace std;
int main() { shared_ptr<int> sp = make_shared<int>(100); int *p = sp.get(); shared_ptr<int> sp2(p); delete p; }
|
1 2 3 4 5 6 7 8 9 10
| #include <iostream> #include <memory>
using namespace std;
int main() { shared_ptr<int> sp = make_shared<int>(100); int *p = sp.get(); }
|
不要将 this 作为 shared_ptr 返回
在 C++ 中,将 this(类对象指针)作为 shared_ptr 返回是危险的,因为 this 本质上是一个原始指针,用它直接构造 shared_ptr 会创建一个独立于现有引用计数体系的新控制块。这会导致同一个对象被多个相互不知晓的 shared_ptr 管理,从而在析构时引发重复释放内存(Double Free)的未定义行为。正确的做法是让类继承自 enable_shared_from_this,然后通过 shared_from_this() 返回安全的智能指针。
1 2 3 4 5 6 7 8 9
| class MyClass { public: shared_ptr<MyClass> getSelf() { return shared_ptr<MyClass>(this); } };
shared_ptr<MyClass> p1 = make_shared<MyClass>(); shared_ptr<MyClass> p2 = p1->getSelf();
|
1 2 3 4 5 6 7 8 9
| class MyClass : public enable_shared_from_this<MyClass> { public: shared_ptr<MyClass> getSelf() { return shared_from_this(); } };
shared_ptr<MyClass> p1 = make_shared<MyClass>(); shared_ptr<MyClass> p2 = p1->getSelf();
|
应避免 shared_ptr 发生循环引用
在 C++ 中,循环引用是指两个或多个 shared_ptr 互相持有对方的引用,导致每个对象的引用计数始终无法降为零,从而永远不会被自动析构,造成内存泄漏。这是因为 shared_ptr 基于引用计数机制工作,只有当计数变为零时才会释放对象,而循环引用使得每个对象至少还被一个其他对象强引用着。解决方法是使用 weak_ptr 来打破循环:将其中一个方向的引用改为弱引用,weak_ptr 不会增加引用计数,因此不影响对象的生命周期。当需要访问弱引用所指向的对象时,可以临时将其提升为 shared_ptr,若对象仍存在则操作成功,否则说明对象已被释放。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| class CB;
class CA { public: shared_ptr<CB> m_cb; ~CA() { cout << "~CA()" << endl; } };
class CB { public: shared_ptr<CA> m_ca; ~CB() { cout << "~CB()" << endl; } };
int main() { shared_ptr<CA> sp_ca = make_shared<CA>(); shared_ptr<CB> sp_cb = make_shared<CB>(); sp_ca->m_cb = sp_cb; sp_cb->m_ca = sp_ca; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| class CB;
class CA { public: shared_ptr<CB> m_cb; ~CA() { cout << "~CA()" << endl; } };
class CB { public: weak_ptr<CA> m_ca; ~CB() { cout << "~CB()" << endl; } };
int main() { shared_ptr<CA> sp_ca = make_shared<CA>(); shared_ptr<CB> sp_cb = make_shared<CB>(); sp_ca->m_cb = sp_cb; sp_cb->m_ca = sp_ca; }
|
shared_ptr 的性能分析
shared_ptr 的指针大小
在 C++ 中,weak_ptr 和 shared_ptr 的大小是一样的,是裸指针(通过 new 得到的指针)大小的两倍;其中包括两个指针(如下图所示),第一个是指向 T 类型对象的指针,第二个是指向控制块的指针。
1 2 3 4
| int *p; shared_ptr<int> sp; int length1 = sizeof(p); int length2 = sizeof(sp);
|
![]()
提示
控制块是由第一个指向某个对象的 shared_ptr 创建的。
shared_ptr 的移动语义
1 2 3 4 5 6 7
| shared_ptr<int> sp = make_shared<int>(100); shared_ptr<int> sp2 = (move(sp)); cout << sp2.use_count() << endl;
shared_ptr<int> sp3; sp3 = move(sp2); cout << sp3.use_count() << endl;
|
note
对于 shared_ptr,移动构造函数的效率高过拷贝构造函数,移动赋值运算符的效率高过拷贝赋值运算符,因为拷贝需要增加引用计数,而移动语义不需要增加引用计数。
线程安全问题的解决
使用智能指针(如 shared_ptr)后,可以解决在多个线程同时访问同一个对象(共享对象)时产生的线程安全问题,详细介绍可以看 这里。