大纲
C++ 智能指针
weak_ptr 智能指针
weak_ptr 是 C++ 中的一个类模板,属于智能指针的一种。它指向一个由 shared_ptr 管理的对象,但与 shared_ptr 不同的是,weak_ptr 不控制所指向对象的生命周期。换句话说,将一个 weak_ptr 绑定到 shared_ptr 上,不会增加引用计数。因此,weak_ptr 常用于解决 shared_ptr 可能导致的循环引用问题,同时也可以用来监视 shared_ptr 所管理对象的生命周期。如果需要访问 weak_ptr 所指向的对象,可以调用其 lock() 成员函数,它会返回一个有效的 shared_ptr(如果原对象还存在);若对象已被释放,则返回一个空的 shared_ptr。值得注意的是,weak_ptr 的构造和析构不会增加或减少所指向对象的引用计数。因此,当最后一个管理该对象的 shared_ptr 被销毁时,对象会照常释放,而不会因为仍有 weak_ptr 指向它而延迟或阻止释放。在 C++ 的术语中,通常将 shared_ptr 称为强智能指针(强引用),而将 weak_ptr 称为弱智能指针(弱引用、弱共享)。这种 “弱” 正体现在它不参与对象的生命周期管理上。
特别注意
weak_ptr 不是一种独立的智能指针,不能用于操作所指向的对象,所以它看起来像是一个 shared_ptr 的助手(旁观者),能够监视到所指向的对象是否存在。
weak_ptr 的使用语法
1 2 3
| shared_ptr<int> sp = make_shared<int>(100); weak_ptr<int> wp(sp);
|
1 2 3 4
| shared_ptr<int> sp = make_shared<int>(100); weak_ptr<int> wp; wp = sp;
|
1 2 3 4 5
| shared_ptr<int> sp = make_shared<int>(100); weak_ptr<int> wp(sp); weak_ptr<int> wp2; wp2 = wp;
|
weak_ptr 的常用操作
lock()
lock():作用是检查 weak_ptr 所指向的对象是否存在- 如果存在,则
lock() 会返回一个指向该对象的 shared_ptr(即所指向对象的强引用计数会加一) - 如果不存在,则
lock() 会返回一个空的 shared_ptr(空指针)
1 2 3 4 5 6 7 8 9
| shared_ptr<int> sp = make_shared<int>(100); weak_ptr<int> wp(sp);
shared_ptr<int> sp2 = wp.lock(); if (sp2 != nullptr) { cout << "object value is " << *sp2 << endl; } else { cout << "object not exist" << endl; }
|
程序运行输出的结果如下:
1 2 3 4 5 6 7 8 9 10 11 12
| shared_ptr<int> sp = make_shared<int>(100); weak_ptr<int> wp(sp);
sp.reset();
shared_ptr<int> sp2 = wp.lock(); if (sp2 != nullptr) { cout << "object value is " << *sp2 << endl; } else { cout << "object not exist" << endl; }
|
程序运行输出的结果如下:
use_count()
use_count():返回有多少个 shared_ptr 指向某个对象(即获取与该 weak_ptr 一起共享对象的其他 shared_ptr 的数量),主要用于代码调试目的
1 2 3 4
| shared_ptr<int> sp = make_shared<int>(100); weak_ptr<int> wp(sp); int count = wp.use_count(); cout << count << endl;
|
1 2 3 4 5 6 7 8
| shared_ptr<int> sp = make_shared<int>(100); weak_ptr<int> wp(sp);
sp.reset();
int count = wp.use_count(); cout << count << endl;
|
expired()
expired():监视的对象是否已过期- 已过期是指
weak_ptr 的 use_count() 返回 0(表示该弱智能指针所指向的对象已经不存在)
1 2 3 4
| shared_ptr<int> sp = make_shared<int>(100); weak_ptr<int> wp(sp); bool expired = wp.expired(); cout << expired << endl;
|
1 2 3 4 5 6 7 8
| shared_ptr<int> sp = make_shared<int>(100); weak_ptr<int> wp(sp);
sp.reset();
bool expired = wp.expired(); cout << expired << endl;
|
reset()
reset():将 weak_ptr 设置为空指针,不影响所指向对象的强引用计数(即 shared_ptr 所指向对象的引用计数),但指向该对象的弱引用计数会减少一
1 2 3
| shared_ptr<int> sp = make_shared<int>(100); weak_ptr<int> wp(sp); wp.reset();
|
weak_ptr 的指针大小
在 C++ 中,weak_ptr 和 shared_ptr 的大小是一样的,是裸指针(通过 new 得到的指针)大小的两倍;其中包括两个指针(如下图所示),第一个是指向 T 类型对象的指针,第二个是指向控制块的指针。
1 2 3 4
| int *p; weak_ptr<int> wp; int length1 = sizeof(p); int length2 = sizeof(wp);
|
![]()
unique_ptr 智能指针
C++ 中的 unique_ptr 是一种独占所有权的智能指针,同一时刻只能有一个 unique_ptr 指向某个对象;当该 unique_ptr 被销毁时,它所指的对象也会随之自动销毁。
unique_ptr 的使用语法
正确的使用语法
unique_ptr 的正确用法- 第一种正确用法:
unique_ptr<int> up(new int(100));unique_ptr<MyClass> up(new MyClass());
- 第二种正确用法:
unique_ptr<int> up = make_unique<int>(100);unique_ptr<MyClass> up = make_unique<MyClass>();- 函数模板
make_unique() 是 C++ 14 才开始引入的,C++ 11 并不支持
- 第三种正确用法:
unique_ptr<int> up;up 是指向 int 的智能指针,但目前指向的内存地址为空,即属于空指针
错误的使用语法
unique_ptr 的错误用法- 第一种错误用法(代码编译失败)
unique_ptr<int> up = new int(100);- 智能指针是
explicit,不可以进行隐式类型转换,必须用直接初始化形式
- 第二种错误用法(代码可以正常运行,但不推荐使用)
int *pi = new int(100); unique_ptr<int> upi(pi);- 裸指针与智能指针尽量不要混用,否则会影响代码的健壮性
unique_ptr 的错误操作
在 C++ 中,unique_ptr 是独占式的,不支持拷贝操作(包括拷贝构造函数和拷贝赋值运算符)。
1 2 3 4 5 6
| unique_ptr<int> up1(new int(100)); unique_ptr<int> up2(up1); unique_ptr<int> up2 = up1;
unique_ptr<int> up3; up3 = up1;
|
unique_ptr 的常用操作
get()
get():返回 unique_ptr 存储的裸指针(通过 new 得到的原始指针)unique_ptr 返回的裸指针,千万不要手动 delete,否则会导致程序运行崩溃(未定义行为)- 注意:如果
unique_ptr 释放了所指向对象的内存,那么 get() 返回的裸指针也会变得无效
1 2 3 4
| unique_ptr<int> up(new int(100)); int* p = up.get(); *p = 50; cout << *p << endl;
|
swap()
1 2 3
| unique_ptr<int> up1(new int(100)); unique_ptr<int> up2(new int(200)); up1.swap(up2);
|
release()
unique_ptr 的 release() 函数用于放弃智能指针对其所指向对象的控制权,切断两者之间的联系。该函数调用后会返回原始的裸指针,同时会将 unique_ptr 自身置为空指针;返回的裸指针需要由程序员手动管理,可以对其执行 delete 释放内存,也可以将其用来初始化另一个智能指针,或者给另一个智能指针赋值。特别注意的是,如果调用 release() 后不对返回的裸指针进行任何处理(既不 delete,也不交给另一个智能指针管理),则原本由 unique_ptr 管理的对象将再也无法被自动释放内存,从而导致内存泄漏。
1 2 3
| unique_ptr<int> up1(new int(100)); int* p = up1.release(); unique_ptr<int> up2(p);
|
reset()
reset():用于改变或释放 unique_ptr 所管理的对象reset() 不带参数:- 释放当前
unique_ptr 所指向对象的内存,然后将当前 unique_ptr 置为空指针
reset(newPtr) 带参数:- 释放当前
unique_ptr 所指向对象的内存,然后将当前 unique_ptr 指向新对象(newPtr) - 注意:参数
newPtr 是裸指针(通过 new 得到的原始指针),另外还有两个参数的重载版本 reset(newPtr, deleter),用于替换并指定自定义删除器
- 如果
reset() 抛出异常(如自定义删除器在释放内存时抛出异常),原对象保持原样(强异常保证)
1 2 3 4 5 6 7 8
| unique_ptr<int> up1(new int(100)); up1.reset();
unique_ptr<int> up2(new int(100)); int* p = new int(200); up2.reset(p);
|
= nullptr
= nullptr 会释放 unique_ptr 所指向对象的内存,并将 unique_ptr 置为空指针。
1 2 3 4
| void test04() { unique_ptr<int> up(new int(100)); up = nullptr; }
|
* 解引用
- 通过
* 获取 unique_ptr 所指向的对象
1 2
| unique_ptr<int> sp(new int(100)); cout << *sp << endl;
|
移动语义
unique_ptr 支持移动语义,可以通过移动构造函数或移动赋值运算符将所有权从一个 unique_ptr 转移到另一个 unique_ptr。当 unique_ptr 移动后,原来的 unique_ptr 会变为空指针,不再指向任何任何对象。
1 2
| unique_ptr<int> up1(new int(100)); unique_ptr<int> up2 = move(up1);
|
作为判断条件
- 智能指针(如
unique_ptr)重载了 operator bool,因此它可以被直接用作条件判断:当智能指针指向一个有效对象时,条件为 true;当它为空(即未指向任何对象)时,条件为 false。
1 2 3 4 5 6 7 8 9 10 11
| unique_ptr<int> up(new int(100)); if (up != nullptr) { cout << "not nullptr" << endl; }
up.reset(); if (up != nullptr) { cout << "not nullptr" << endl; } else { cout << "nullptr" << endl; }
|
返回 unique_ptr 类型
- 虽然
unique_ptr 是独占式的,不支持拷贝操作(包括拷贝构造函数和拷贝赋值运算符);但是,当 unique_ptr 即将被销毁的时候,它是可以拷贝的。最常见的用法就是从函数返回一个 unique_ptr。
1 2 3 4 5 6 7 8 9 10 11
| unique_ptr<int> func() { unique_ptr<int> up(new int(100)); return up; }
int main() { unique_ptr<int> up = func();
unique_ptr<int> up2; up2 = func(); }
|
转换为 shared_ptr 类型
1 2 3 4 5 6 7 8
| unique_ptr<int> func() { return unique_ptr<int>(new int(100)); }
int main() { shared_ptr<int> sp = func(); return 0; }
|
1 2 3 4 5
| int main() { unique_ptr<int> up(new int(100)); shared_ptr<int> sp = move(up); }
|
unique_ptr 的删除器
自定义删除器
unique_ptr 会在一定的时机自动删除所指向的对象,并且底层使用 delete 操作符作为默认的资源析构方式。unique_ptr 还支持指定自定义的删除器来取代 C++ 编译器提供的默认删除器,语法格式为:unique_ptr<对象类型, 删除器类型> 智能指针变量名。unique_ptr 的删除器可以是任何可调用对象,例如普通函数、重载了 operator() 的类,或者 Lambda 表达式。
第一种写法(使用普通函数自定义删除器)
1 2 3 4 5 6 7 8 9 10 11 12 13
| void myDeleter(int *p) { delete p; p = nullptr; cout << "delete int *" << endl; }
int main() { typedef void (*func)(int *); unique_ptr<int, func> up(new int(100), myDeleter); return 0; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| void myDeleter(int *p) { delete p; p = nullptr; cout << "delete int *" << endl; }
int main() { using func = void (*)(int *); unique_ptr<int, func> up(new int(100), myDeleter); return 0; }
|
1 2 3 4 5 6 7 8 9 10 11 12
| void myDeleter(int *p) { delete p; p = nullptr; cout << "delete int *" << endl; }
int main() { unique_ptr<int, void (*)(int *)> up(new int(100), myDeleter); return 0; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| void myDeleter(int *p) { delete p; p = nullptr; cout << "delete int *" << endl; }
int main() { typedef decltype(myDeleter) *func; unique_ptr<int, func> up(new int(100), myDeleter);
using func2 = decltype(myDeleter) *; unique_ptr<int, func2> up2(new int(100), myDeleter);
unique_ptr<int, decltype(myDeleter) *> up3(new int(100), myDeleter); return 0; }
|
第二种写法(使用 Lambda 表达式自定义删除器)
1 2 3 4 5 6 7 8 9 10 11 12
| auto myDelLambda = [](int *p) { delete p; p = nullptr; cout << "delete int *" << endl; };
int main() { unique_ptr<int, decltype(myDelLambda)> up(new int(100), myDelLambda); return 0; }
|
1 2 3 4 5 6 7 8 9
| int main() { unique_ptr<int, void (*)(int *)> up(new int(100), [](int *p) { delete p; p = nullptr; cout << "delete int *" << endl; }); return 0; }
|
使用数组的问题
如果 unique_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() { unique_ptr<MyClass[], void (*)(MyClass *)> sp2(new MyClass[3], [](MyClass *p) { delete[] p; cout << "delete MyClass []" << endl; }); }
|
程序运行输出的结果如下:
1 2 3 4
| ~MyClass() ~MyClass() ~MyClass() delete MyClass []
|
还可以使用数组特化版本 unique_ptr<T[]>,同样可以正确释放数组内存(从 C++ 17 开始支持以下写法)
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() { unique_ptr<MyClass[]> up(new MyClass[3]); }
|
程序运行输出的结果如下:
1 2 3
| ~MyClass() ~MyClass() ~MyClass()
|
还可以使用 std::default_delete 来做删除器,std::default_delete 是标准库(STL)里的模板类,同样可以正确释放数组的内存(从 C++ 17 开始支持以下写法)
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() { unique_ptr<MyClass[], default_delete<MyClass[]>> up(new MyClass[3]); }
|
程序运行输出的结果如下:
1 2 3
| ~MyClass() ~MyClass() ~MyClass()
|
不是同一种类型的说明
- 如果两个
shared_ptr 指定了不同的删除器,只要它们所指向的对象类型是相同的,那么这两个 shared_ptr 也属于同一种类型。这就代表只要类型相同,就可以将它们放入到元素类型为该对象类型的容器里面。 - 但是,对于
unique_ptr 来说,指定 unique_ptr 的删除器后,会影响 unique_ptr 的类型。 - 简而言之,如果两个
unique_ptr 的删除器类型不同,那么即使它们指向的对象类型相同,这两个 unique_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; };
unique_ptr<int, decltype(lambda1)> up1(new int(100), lambda1);
unique_ptr<int, decltype(lambda2)> up2(new int(200), lambda2);
vector<unique_ptr<int, decltype(lambda1)>> vec; vec.emplace_back(move(up1));
return 0; }
|
unique_ptr 的性能分析
unique_ptr 的大小
在 C++ 中,unique_ptr 的大小通常与裸指针(通过 new 得到的指针)相同。但是,如果 unique_ptr 指定了自定义删除器(尤其是带有状态的删除器,如函数对象或 Lambda 表达式捕获了变量),则其大小可能超过裸指针的大小。
- 默认删除器(
std::default_delete)无状态,通常不增加额外大小。 - 无捕获的 Lambda 表达式作为删除器时,由于继承空基类优化(EBO),也通常不增加大小。
- 使用函数指针作为删除器时,会额外占用一个指针的大小。
- 有捕获的 Lambda 表达式或自定义类删除器带有成员变量时,会增加相应大小。
1 2 3 4 5 6
| int main() { int *p; unique_ptr<int> up; int length1 = sizeof(p); int length2 = sizeof(up); }
|
1 2 3 4 5 6 7 8 9 10 11
| void myDeleter(int *p) { delete p; p = nullptr; cout << "delete int *" << endl; }
int main() { unique_ptr<int, void (*)(int *)> up3(new int(100), myDeleter); int length = sizeof(up3); cout << length << endl; }
|
unique_ptr 的选择建议
- 如果程序需要多个指针共享同一个对象,应选择
shared_ptr。 - 如果程序不需要多个指针共享同一个对象,应优先使用
unique_ptr,性能更高。
auto_ptr 智能指针
auto_ptr 为什么会被废弃
auto_ptr 是 C++ 98 时代的智能指针,其被废弃的主要原因是:它使用拷贝语义实现移动所有权,导致拷贝后原指针变为空,违反了常规拷贝的直觉,极易引发运行时错误。C++ 11 引入移动语义后,由 unique_ptr 安全替代 auto_ptr。
1 2
| auto_ptr<int> ap1(new int(100)); auto_ptr<int> ap2 = ap1;
|