左值(lvalue)是指那些有明确内存位置(有标识符)的表达式,可以出现在赋值语句的左侧。
特点:
有明确的内存地址
可以取地址(可以用&运算符获取其地址)
可以被修改(除非是const)
通常具有名称
例如:
1int x = 10; // x是左值
2int* p = &x; // 可以取x的地址,因为x是左值
3x = 20; // 可以对x赋值,因为x是左值
右值(rvalue)是指那些临时的、无法获取地址的表达式,只能出现在赋值语句的右侧。
特点:
没有明确的内存地址(或者说地址不可访问)
不能取地址(不能用&运算符)
通常是临时的
不能被赋值
例如:
xxxxxxxxxx
31int x = 10 + 20; // 10+20是右值
2// int* p = &(10 + 20); // 错误!不能取右值的地址
3// 10 = x; // 错误!不能给右值赋值
左值引用 (lvalue reference)使用&
符号声明,只能绑定到左值:
xxxxxxxxxx
31int x = 10;
2int& ref = x; // 正确:左值引用绑定到左值
3// int& ref2 = 10; // 错误:左值引用不能绑定到右值
例外:常量左值引用可以绑定到右值:
xxxxxxxxxx
11const int& ref = 10; // 正确:常量左值引用可以绑定到右值
右值引用 (rvalue reference)(C++11引入)使用&&
符号声明,只能绑定到右值:
xxxxxxxxxx
31int&& rref = 10; // 正确:右值引用绑定到右值
2int x = 10;
3// int&& rref2 = x; // 错误:右值引用不能绑定到左值
右值引用的主要用途是实现移动语义,避免不必要的拷贝:
xxxxxxxxxx
31std::string str1 = "Hello";
2std::string str2 = std::move(str1); // str1的内容被"移动"到str2
3// 此时str1可能为空,其资源已转移到str2
完美转发 (perfect forwarding)
使用std::forward
保持参数的值类别(左值/右值):
xxxxxxxxxx
41template<typename T>
2void wrapper(T&& param) {
3 foo(std::forward<T>(param)); // 保持param的值类别
4}
C++11进一步细分了表达式的值类别:
泛左值 (glvalue)
左值 (lvalue): 有标识符的表达式
亡值 (xvalue): 即将被销毁、资源可以被重用的表达式(如std::move
的结果)
右值 (rvalue)
亡值 (xvalue): 同上
纯右值 (prvalue): 字面量、临时对象等
能否取地址:如果可以对表达式使用&运算符,则它是左值;否则是右值
能否赋值:如果表达式可以出现在赋值语句左侧,则它是左值;否则是右值
有无名称:有名称的通常是左值,无名称的通常是右值
变量名:int x; x是左值
数组元素:arr[0]
解引用操作:*ptr
前置递增/递减:++x, --x
赋值表达式:x = 1
(整个表达式是左值)
字符串字面量:"hello"
(特例)
字面量:42, true, 3.14
算术表达式:x + y, x * y
后置递增/递减:x++, x--
函数调用(返回非引用类型):getValue()
临时对象:MyClass()
移动构造函数是C++11引入的一种特殊构造函数,用于从一个临时对象(右值)高效地"窃取"资源,而不是进行昂贵的深拷贝操作。它的主要目的是优化性能,特别是对于包含动态分配资源(如堆内存、文件句柄等)的类。
移动构造函数的声明语法如下:
xxxxxxxxxx
51class MyClass {
2public:
3 // 移动构造函数
4 MyClass(MyClass&& other) noexcept;
5};
关键特点:
参数是右值引用(&&
)
通常标记为noexcept
(不抛出异常)
实现中会"窃取"源对象的资源,并将源对象置于有效但未指定的状态
简单字符串类的移动构造函数
x1class MyString {
2private:
3 char* data;
4 size_t length;
5
6public:
7 // 普通构造函数
8 MyString(const char* str) {
9 length = strlen(str);
10 data = new char[length + 1];
11 strcpy(data, str);
12 }
13
14 // 拷贝构造函数
15 MyString(const MyString& other) {
16 length = other.length;
17 data = new char[length + 1]; // 分配新内存
18 strcpy(data, other.data); // 复制数据
19 }
20
21 // 移动构造函数
22 MyString(MyString&& other) noexcept {
23 // 窃取资源
24 data = other.data;
25 length = other.length;
26
27 // 将源对象置于有效但未指定的状态
28 other.data = nullptr;
29 other.length = 0;
30 }
31
32 // 析构函数
33 ~MyString() {
34 delete[] data;
35 }
36};
移动构造函数的工作原理
资源转移而非复制:移动构造函数不创建新资源,而是转移现有资源的所有权
指针窃取:通常通过浅拷贝指针来实现资源转移
源对象重置:将源对象的指针设为nullptr
,防止资源被多次释放
零开销抽象:理想情况下,移动操作只涉及几个指针赋值,几乎没有性能开销
何时调用移动构造函数
移动构造函数在以下情况会被调用:
使用std::move
显式转换:
xxxxxxxxxx
21MyString str1 = "Hello";
2MyString str2 = std::move(str1); // 调用移动构造函数
返回值优化失败时的函数返回:
xxxxxxxxxx
41MyString createString() {
2 MyString temp("Hello");
3 return temp; // 可能调用移动构造函数(如果RVO失败)
4}
右值参数传递:
xxxxxxxxxx
21void process(MyString str) { /*...*/ }
2process(MyString("Hello")); // 临时对象通过移动构造传递
特性 | 拷贝构造函数 | 移动构造函数 |
---|---|---|
参数类型 | const T& | T&& |
源对象 | 保持不变 | 被修改(资源被窃取) |
资源处理 | 深拷贝(分配新资源) | 转移所有权(无新分配) |
性能 | 较慢(需要内存分配和复制) | 快(仅指针操作) |
异常安全 | 可能抛出异常 | 通常标记为noexcept |
当返回的对象其本身生命周期即将结束,就不再调用拷贝构造函数,而是调用移动构造函数。
你问得非常好!这确实是理解移动语义的关键点。让我详细解释不同情况下移动构造的行为。
"夺取指针":
xxxxxxxxxx
121class WithPointer {
2private:
3 int* data;
4 size_t size;
5public:
6 // 移动构造:转移指针所有权
7 WithPointer(WithPointer&& other) noexcept
8 : data(other.data), size(other.size) {
9 other.data = nullptr; // 夺取指针
10 other.size = 0;
11 }
12};
关键:如果对象没有动态资源,移动构造通常退化为拷贝构造!
xxxxxxxxxx
121class SimpleClass {
2private:
3 int value;
4 double data;
5public:
6 // 即使定义了移动构造函数,实际上还是拷贝
7 SimpleClass(SimpleClass&& other) noexcept
8 : value(other.value), data(other.data) {
9 // 没有指针可以"夺取",只能拷贝值
10 // other的值保持不变
11 }
12};
对于没有动态资源的简单类型,编译器通常会:
xxxxxxxxxx
101class SimpleInt {
2 int value;
3public:
4 // 编译器可能根本不生成移动构造函数
5 // 或者生成的移动构造函数等同于拷贝构造函数
6};
7
8// 这两个调用可能生成相同的汇编代码
9vec.push_back(obj); // 拷贝
10vec.push_back(std::move(obj)); // "移动",但实际还是拷贝
移动构造真正有意义的情况:
xxxxxxxxxx
151class MeaningfulMove {
2private:
3 std::vector<int> data; // 内部有动态内存
4 std::string name; // 内部有动态内存
5 std::unique_ptr<int> ptr; // 智能指针
6
7public:
8 // 移动构造:转移内部容器的资源
9 MeaningfulMove(MeaningfulMove&& other) noexcept
10 : data(std::move(other.data)), // vector的移动
11 name(std::move(other.name)), // string的移动
12 ptr(std::move(other.ptr)) { // unique_ptr的移动
13 // other的内部容器变为空状态
14 }
15};
标准库容器的移动
xxxxxxxxxx
81void containerMoveExample() {
2 std::vector<int> vec1 = {1, 2, 3, 4, 5};
3 std::cout << "移动前 vec1.size(): " << vec1.size() << std::endl;
4
5 std::vector<int> vec2 = std::move(vec1);
6 std::cout << "移动后 vec1.size(): " << vec1.size() << std::endl; // 0
7 std::cout << "移动后 vec2.size(): " << vec2.size() << std::endl; // 5
8}
总结
移动构造的资源转移取决于对象的内部结构:
对象类型 | 移动行为 | 性能提升 |
---|---|---|
有动态资源(指针、容器等) | 真正的资源转移 | 显著 |
只有基本类型成员 | 退化为拷贝 | 无或微小 |
包含可移动的成员 | 递归调用成员的移动 | 取决于成员 |
关键点:
如果没有指针等动态资源,移动构造通常等同于拷贝构造
移动的意义在于避免昂贵的深拷贝操作
对于简单的值类型,移动和拷贝的成本相同
真正的性能提升来自于避免动态内存分配和大量数据复制
如果原对象没有指针或其他动态资源,移动构造基本上没有资源可以转移,性能提升也就微乎其微。
1. 智能指针的移动构造
xxxxxxxxxx
141template <class T>
2class unique_ptr {
3private:
4 T* ptr;
5
6public:
7 // 移动构造函数
8 unique_ptr(unique_ptr&& other) noexcept : ptr(other.ptr) {
9 other.ptr = nullptr;
10 }
11
12 // 禁用拷贝构造
13 unique_ptr(const unique_ptr&) = delete;
14};
2. 容器类的移动构造
xxxxxxxxxx
181template <class T>
2class Vector {
3private:
4 T* elements;
5 size_t size;
6 size_t capacity;
7
8public:
9 // 移动构造函数
10 Vector(Vector&& other) noexcept
11 : elements(other.elements),
12 size(other.size),
13 capacity(other.capacity) {
14 other.elements = nullptr;
15 other.size = 0;
16 other.capacity = 0;
17 }
18};
移动赋值函数(Move Assignment Operator)是C++11引入的一种特殊赋值运算符,用于实现资源的高效转移而非复制。它的主要目的是避免不必要的深拷贝操作,通过"窃取"右值对象的资源来提高性能。
移动赋值函数的典型声明如下:
xxxxxxxxxx
11ClassName& operator=(ClassName&& other) noexcept;
关键特点:
参数是一个右值引用(&&
)
通常标记为noexcept
以便标准库容器能够进行优化
返回当前对象的引用,以支持连续赋值操作
移动赋值函数的基本实现步骤:
释放当前对象拥有的资源
从参数对象"窃取"资源(转移所有权)
将参数对象置于有效但未指定的状态(通常是空/null状态)
返回当前对象的引用
字符串类示例
xxxxxxxxxx
261class MyString {
2private:
3 char* data;
4 size_t length;
5
6public:
7 // 构造函数和析构函数省略...
8
9 // 移动赋值函数
10 MyString& operator=(MyString&& other) noexcept {
11 // 避免自我赋值
12 if (this != &other) {
13 // 释放当前资源
14 delete[] data;
15
16 // 窃取资源
17 data = other.data;
18 length = other.length;
19
20 // 将源对象置于有效但未指定状态
21 other.data = nullptr;
22 other.length = 0;
23 }
24 return *this;
25 }
26};
智能指针示例
xxxxxxxxxx
211template <typename T>
2class UniquePtr {
3private:
4 T* ptr;
5
6public:
7 // 移动赋值函数
8 UniquePtr& operator=(UniquePtr&& other) noexcept {
9 if (this != &other) {
10 // 释放当前资源
11 delete ptr;
12
13 // 转移所有权
14 ptr = other.ptr;
15
16 // 重置源对象
17 other.ptr = nullptr;
18 }
19 return *this;
20 }
21};
特性 | 移动赋值 | 复制赋值 |
---|---|---|
参数类型 | 右值引用 (T&& ) | 常量左值引用 (const T& ) |
资源处理 | 转移所有权 | 创建资源副本 |
源对象状态 | 被修改(通常置空) | 保持不变 |
性能 | 高效(避免深拷贝) | 可能较慢(需要深拷贝) |
适用场景 | 临时对象、即将销毁的对象 | 需要保留源对象的情况 |
移动赋值函数在以下情况下会被调用:
显式使用std::move
将左值转换为右值引用:
xxxxxxxxxx
21MyString a, b;
2a = std::move(b); // 调用移动赋值
赋值临时对象(右值):
xxxxxxxxxx
21MyString a;
2a = MyString("temp"); // 调用移动赋值
在标准库容器中,当元素需要移动而非复制时
std::move
是C++11引入的一个标准库函数,定义在 <utility>
头文件中。它的主要作用是将一个左值(lvalue)转换为右值引用(rvalue reference),从而使移动语义得以实现。
函数原型:
xxxxxxxxxx
21template <typename T>
2constexpr typename std::remove_reference<T>::type&& move(T&& t) noexcept;
在C++14及以后的版本中,实现更简洁:
xxxxxxxxxx
41template <typename T>
2constexpr auto&& move(T&& t) noexcept {
3 return static_cast<typename std::remove_reference<T>::type&&>(t);
4}
std::move
本质上是一个类型转换函数,它并不会真正"移动"任何东西。它的核心功能是:
接受一个通用引用参数 T&&
使用 std::remove_reference
移除引用
将参数转换为右值引用类型
简单来说,std::move
告诉编译器:"我不再需要这个对象的值,你可以自由地移动它的资源。"
1. 实现移动构造函数和移动赋值运算符
xxxxxxxxxx
251class MyString {
2private:
3 char* data;
4 size_t length;
5
6public:
7 // 移动构造函数
8 MyString(MyString&& other) noexcept
9 : data(other.data), length(other.length) {
10 other.data = nullptr;
11 other.length = 0;
12 }
13
14 // 移动赋值运算符
15 MyString& operator=(MyString&& other) noexcept {
16 if (this != &other) {
17 delete[] data;
18 data = other.data;
19 length = other.length;
20 other.data = nullptr;
21 other.length = 0;
22 }
23 return *this;
24 }
25};
2. 转移对象所有权
xxxxxxxxxx
21std::unique_ptr<int> p1(new int(42));
2std::unique_ptr<int> p2 = std::move(p1); // p1现在为nullptr
3. 在容器操作中提高效率
xxxxxxxxxx
41std::vector<MyString> vec;
2MyString str("Hello");
3vec.push_back(std::move(str)); // 使用移动语义而非复制
4// 此时str仍然有效,但其内容已被移动
4. 强制使用移动语义
xxxxxxxxxx
41MyString getString() {
2 MyString result("Hello");
3 return std::move(result); // 通常不必要,因为RVO会优化
4}
Note
移动后的对象状态:被移动后的对象仍然有效,但处于未指定(但有效)的状态。通常,标准库实现会将被移动的对象置于一个"空"状态。
不要对常量对象使用 std::move
:常量对象不能被移动,因为移动操作需要修改源对象。
返回值优化 (RVO):在函数返回局部对象时,通常不需要使用 std::move
,因为编译器会应用返回值优化。
不要在 return
语句中对函数参数使用 std::move
:
xxxxxxxxxx
51// 错误用法
2template <typename T>
3T&& forward(T&& param) {
4 return std::move(param); // 错误!应该使用std::forward
5}
与 std::forward
的区别:
std::move
无条件将参数转换为右值引用
std::forward
有条件地转换,只在输入是右值时才转换为右值引用
交换两个对象
xxxxxxxxxx
61template <typename T>
2void swap(T& a, T& b) {
3 T temp = std::move(a);
4 a = std::move(b);
5 b = std::move(temp);
6}
在容器中重用内存
xxxxxxxxxx
51std::vector<std::string> v1 = {"large string 1", "large string 2"};
2std::vector<std::string> v2;
3
4// 高效地将v1的内容移动到v2
5v2 = std::move(v1);
实现移动语义的工厂函数
xxxxxxxxxx
41template <typename... Args>
2std::unique_ptr<Widget> makeWidget(Args&&... args) {
3 return std::unique_ptr<Widget>(new Widget(std::forward<Args>(args)...));
4}
右值引用本身是左值还是右值,取决于是否有名字,有名字就是左值,没名字就是右值。
RAII (Resource Acquisition Is Initialization,资源获取即初始化)是C++中一种重要的编程技术和设计模式。
RAII的基本思想是:将资源的生命周期与对象的生命周期绑定。当对象创建时获取资源,当对象销毁时自动释放资源。
RAII技术,具备以下基本特征:
在构造函数中获取资源;(在给构造函数传参时初始化资源)
在析构函数中释放资源;
一般不允许进行复制或者赋值(对象语义),避免;
提供若干访问资源的方法(如:读写文件)。
我们可以实现以下的一个类模板,模拟RAII的思想
xxxxxxxxxx
471template <class T>
2class RAII
3{
4public:
5 //1.在构造函数中初始化资源(托管资源)
6 RAII(T * data)
7 : m_data(data)
8 {
9 cout << "RAII(T*)" << endl;
10 }
11
12 //2.在析构函数中释放资源
13 ~RAII(){
14 cout << "~RAII()" << endl;
15 if(m_data){
16 delete m_data;
17 m_data = nullptr;
18 }
19 }
20
21 //3.提供若干访问资源的方法
22 T * operator->(){
23 return m_data;
24 }
25
26 T & operator*(){
27 return *m_data;
28 }
29
30 T * get() const{
31 return m_data;
32 }
33
34 void set(T * data){
35 if(m_data){
36 delete m_data;
37 m_data = nullptr;
38 }
39 m_data = data;
40 }
41
42 //4.不允许复制或赋值
43 RAII(const RAII & rhs) = delete;
44 RAII& operator=(const RAII & rhs) = delete;
45private:
46 T * m_data;
47};
如下,raii不是一个指针,而是一个对象,但是它的使用已经和指针完全一致了。这个对象可以托管堆上的Point对象,而且不用考虑delete。
xxxxxxxxxx
71void test0() {
2 Point * pt = new Point(1, 2);
3 //智能指针的雏形
4 RAII<Point> raii(pt);
5 raii->print();
6 (*raii).print();
7}
RAII技术的本质:利用栈对象的生命周期管理资源,因为栈对象在离开作用域时候,会执行析构函数。
智能指针是C++中用于自动管理动态内存的重要工具,它们通过RAII技术确保内存的正确分配和释放,有效防止内存泄漏和悬空指针问题。
unique_ptr
表示对资源的独占所有权,不能被复制,只能被移动。
特点1:不允许复制或者赋值
具备对象语义。
特点2:独享所有权的智能指针
特点3:作为容器元素
要利用移动语义的特点,可以直接传递右值属性的unique_ptr作为容器的元素。如果传入左值形态的unique_ptr,会进行复制操作,而unique_ptr是不能复制的。
构建右值的方式有
1、std::move的方式
2、可以直接使用unique_ptr的构造函数,创建匿名对象(临时对象),构建右值。
xxxxxxxxxx
101vector<unique_ptr<Point>> vec;
2unique_ptr<Point> up4(new Point(10,20));
3//up4是一个左值
4//将up4这个对象作为参数传给了push_back函数,会调用拷贝构造
5//但是unique_ptr的拷贝构造已经删除了
6//所以这样写会报错
7vec.push_back(up4); //error
8
9vec.push_back(std::move(up4)); //ok
10vec.push_back(unique_ptr<Point>(new Point(1,3))); //ok
xxxxxxxxxx
121//将unique_ptr作为容器元素时,只能传入右值
2std::vector<unique_ptr<int>> vec;
3unique_ptr<int> up{new int{10}};
4// vector的元素会存在堆上, up本身是一个栈上的对象
5// 这里会发生unique_ptr的复制, 调用已经被删除的拷贝构造函数
6/* vec.push_back(up); //error不能添加左值*/
7
8vec.push_back(unique_ptr<int>{new int{100}});
9vec.push_back(std::move(up))
10
11cout << &up << endl;//栈上地址 up变量本身地址
12cout << &vec[1] << endl;//堆上地址 vector内部存储该的unique_ptr对象的地址
xxxxxxxxxx
631
2
3
4
5class MyClass {
6public:
7 MyClass(int value) : data(value) {
8 std::cout << "MyClass constructed: " << data << std::endl;
9 }
10
11 ~MyClass() {
12 std::cout << "MyClass destructed: " << data << std::endl;
13 }
14
15 void print() const {
16 std::cout << "Value: " << data << std::endl;
17 }
18
19private:
20 int data;
21};
22
23void demonstrateUniquePtr() {
24 std::cout << "=== unique_ptr Demo ===" << std::endl;
25
26 // 创建unique_ptr的几种方式
27 std::unique_ptr<MyClass> ptr1 = std::make_unique<MyClass>(42);
28 std::unique_ptr<MyClass> ptr2(new MyClass(100));
29 auto ptr3 = std::make_unique<MyClass>(200);
30
31 // 使用智能指针
32 ptr1->print();
33 (*ptr2).print();
34
35 // 检查是否为空
36 if (ptr1) {
37 std::cout << "ptr1 is not null" << std::endl;
38 }
39
40 // 获取原始指针(谨慎使用)
41 MyClass* raw = ptr1.get();
42
43 // 释放所有权
44 MyClass* released = ptr1.release();
45 if (!ptr1) {
46 std::cout << "ptr1 is now null" << std::endl;
47 }
48 delete released; // 需要手动删除
49
50 // 重置指针
51 ptr2.reset(); // 删除当前对象
52 ptr3.reset(new MyClass(300)); // 删除当前对象并指向新对象
53
54 // 移动语义
55 std::unique_ptr<MyClass> ptr4 = std::move(ptr3);
56 // ptr3现在为空,ptr4拥有对象
57
58 // 数组版本
59 std::unique_ptr<int[]> arr = std::make_unique<int[]>(5);
60 for (int i = 0; i < 5; ++i) {
61 arr[i] = i * 10;
62 }
63}
shared_ptr
允许多个指针共享同一个对象,使用引用计数管理对象生命周期。
特征1:共享所有权的智能指针
可以使用引用计数记录对象的个数。
特征2:可以进行复制或者赋值
表明具备值语义。
特征3:也可以作为容器的元素
作为容器元素的时候,即可以传递左值,也可以传递右值。(区别于unique_ptr只能传右值)
特征4:也具备移动语义
表明也有移动构造函数与移动赋值函数。
xxxxxxxxxx
491
2
3
4
5void demonstrateSharedPtr() {
6 std::cout << "\n=== shared_ptr Demo ===" << std::endl;
7
8 // 创建shared_ptr
9 std::shared_ptr<MyClass> ptr1 = std::make_shared<MyClass>(42);
10 std::cout << "Reference count: " << ptr1.use_count() << std::endl;
11
12 {
13 // 复制shared_ptr
14 std::shared_ptr<MyClass> ptr2 = ptr1;
15 std::shared_ptr<MyClass> ptr3(ptr1);
16
17 std::cout << "Reference count: " << ptr1.use_count() << std::endl;
18
19 ptr2->print();
20 ptr3->print();
21
22 // 检查是否指向同一对象
23 if (ptr1 == ptr2) {
24 std::cout << "ptr1 and ptr2 point to the same object" << std::endl;
25 }
26 } // ptr2和ptr3离开作用域,引用计数减少
27
28 std::cout << "Reference count after scope: " << ptr1.use_count() << std::endl;
29
30 // 重置
31 ptr1.reset();
32 std::cout << "Object destroyed when last shared_ptr is reset" << std::endl;
33}
34
35// shared_ptr与容器
36void demonstrateSharedPtrWithContainers() {
37 std::cout << "\n=== shared_ptr with Containers ===" << std::endl;
38
39 std::vector<std::shared_ptr<MyClass>> vec;
40
41 auto ptr = std::make_shared<MyClass>(999);
42 vec.push_back(ptr);
43 vec.push_back(ptr); // 同一对象被多次添加
44
45 std::cout << "Reference count: " << ptr.use_count() << std::endl;
46
47 vec.clear();
48 std::cout << "Reference count after clear: " << ptr.use_count() << std::endl;
49}
shared_ptr
的循环引用是智能指针使用中的一个重要问题,它会导致内存泄漏,因为相互引用的对象永远不会被销毁。
xxxxxxxxxx
741
2
3
4class Child;
5
6class Parent {
7public:
8 Parent(const std::string& name) : name_(name) {
9 std::cout << "Parent " << name_ << " created" << std::endl;
10 }
11
12 ~Parent() {
13 std::cout << "Parent " << name_ << " destroyed" << std::endl;
14 }
15
16 void addChild(std::shared_ptr<Child> child) {
17 child_ = child;
18 }
19
20 void printInfo() const {
21 std::cout << "Parent: " << name_ << std::endl;
22 }
23
24private:
25 std::string name_;
26 std::shared_ptr<Child> child_; // 持有Child的shared_ptr
27};
28
29class Child {
30public:
31 Child(const std::string& name) : name_(name) {
32 std::cout << "Child " << name_ << " created" << std::endl;
33 }
34
35 ~Child() {
36 std::cout << "Child " << name_ << " destroyed" << std::endl;
37 }
38
39 void setParent(std::shared_ptr<Parent> parent) {
40 parent_ = parent;
41 }
42
43 void printInfo() const {
44 std::cout << "Child: " << name_ << std::endl;
45 }
46
47private:
48 std::string name_;
49 std::shared_ptr<Parent> parent_; // 持有Parent的shared_ptr - 问题所在!
50};
51
52void demonstrateCircularReference() {
53 std::cout << "=== 循环引用问题演示 ===" << std::endl;
54
55 {
56 auto parent = std::make_shared<Parent>("Alice");
57 auto child = std::make_shared<Child>("Bob");
58
59 std::cout << "Parent引用计数: " << parent.use_count() << std::endl;
60 std::cout << "Child引用计数: " << child.use_count() << std::endl;
61
62 // 建立循环引用
63 parent->addChild(child);
64 child->setParent(parent);
65
66 std::cout << "建立关系后:" << std::endl;
67 std::cout << "Parent引用计数: " << parent.use_count() << std::endl;
68 std::cout << "Child引用计数: " << child.use_count() << std::endl;
69
70 } // parent和child离开作用域
71
72 std::cout << "离开作用域后..." << std::endl;
73 // 注意:析构函数不会被调用!内存泄漏!
74}
问题分析
Parent对象持有shared_ptr<Child>
,引用计数为1
Child对象持有shared_ptr<Parent>
,引用计数为1
当局部变量parent
和child
离开作用域时:
parent
的引用计数从2变为1(Child仍持有引用)
child
的引用计数从2变为1(Parent仍持有引用)
两个对象都不会被销毁,造成内存泄漏
xxxxxxxxxx
271
2
3
4template<typename T>
5class CircularReferenceDetector {
6public:
7 static bool hasCircularReference(const std::shared_ptr<T>& ptr) {
8 std::unordered_set<T*> visited;
9 return detectCycle(ptr.get(), visited);
10 }
11
12private:
13 static bool detectCycle(T* current, std::unordered_set<T*>& visited) {
14 if (!current) return false;
15
16 if (visited.find(current) != visited.end()) {
17 return true; // 发现循环
18 }
19
20 visited.insert(current);
21
22 // 这里需要根据具体类型实现遍历逻辑
23 // 例如,对于树结构,遍历所有子节点
24
25 return false;
26 }
27};
解决方案:使用weak_ptr
weak_ptr
提供对shared_ptr
管理对象的弱引用,不影响引用计数,主要用于解决循环引用问题。
weak_ptr
知道所托管的对象是否还存活,如果存活,必须要提升为shared_ptr才能对资源进行访问,不能直接访问。
xxxxxxxxxx
601
2
3
4class Node {
5public:
6 Node(int val) : value(val) {
7 std::cout << "Node " << value << " created" << std::endl;
8 }
9
10 ~Node() {
11 std::cout << "Node " << value << " destroyed" << std::endl;
12 }
13
14 void setNext(std::shared_ptr<Node> next) {
15 this->next = next;
16 }
17
18 void setParent(std::shared_ptr<Node> parent) {
19 this->parent = parent; // 使用weak_ptr避免循环引用
20 }
21
22 void print() const {
23 std::cout << "Node value: " << value << std::endl;
24 if (auto p = parent.lock()) { // 安全访问weak_ptr
25 std::cout << " Parent: " << p->value << std::endl;
26 }
27 }
28
29private:
30 int value;
31 std::shared_ptr<Node> next;
32 std::weak_ptr<Node> parent; // 使用weak_ptr避免循环引用
33};
34
35void demonstrateWeakPtr() {
36 std::cout << "\n=== weak_ptr Demo ===" << std::endl;
37
38 auto parent = std::make_shared<Node>(1);
39 auto child = std::make_shared<Node>(2);
40
41 parent->setNext(child);
42 child->setParent(parent);
43
44 std::cout << "Parent reference count: " << parent.use_count() << std::endl;
45 std::cout << "Child reference count: " << child.use_count() << std::endl;
46
47 child->print();
48
49 // 创建weak_ptr
50 std::weak_ptr<Node> weak = parent;
51 std::cout << "Weak ptr expired: " << weak.expired() << std::endl;
52
53 // 安全访问weak_ptr指向的对象
54 if (auto locked = weak.lock()) {
55 locked->print();
56 }
57
58 parent.reset();
59 std::cout << "After parent reset, weak ptr expired: " << weak.expired() << std::endl;
60}
判断关联的空间是否还在
1.可以直接使用use_count函数
如果use_count的返回值大于0,表明关联的空间还在
2.将weak_ptr提升为shared_ptr
xxxxxxxxxx
31shared_ptr<int> sp(new int(10));
2weak_ptr<int> wp;//无参的方式创建weak_ptr
3wp = sp;//赋值
这种赋值操作可以让wp也能够托管这片空间,但是它作为一个weak_ptr仍不能够去管理,甚至连访问都不允许(weak_ptr不支持直接解引用)
想要真正地去进行管理需要使用lock函数将weak_ptr提升为shared_ptr
xxxxxxxxxx
71shared_ptr<int> sp2 = wp.lock();
2if(sp2){
3cout << "提升成功" << endl;
4cout << *sp2 << endl;
5}else{
6cout << "提升失败,托管的空间已经被销毁" << endl;
7}
如果托管的资源没有被销毁,就可以成功提升为shared_ptr,否则就会返回一个空的shared_ptr(空指针)
查看lock函数的说明
xxxxxxxxxx
21std::shared_ptr<T> lock() const noexcept;
2//将weak_ptr提升成一个shared_ptr,然后再来判断shared_ptr,进而知道weak_ptr指向的空间还在不在
3.可以使用expired函数
xxxxxxxxxx
21bool expired() const noexcept;
2//weak_ptr去判断托管的资源有没有被回收
该函数返回true等价于use_count() == 0.
xxxxxxxxxx
61bool flag = wp.expired();
2if(flag){
3cout << "托管的空间已经被销毁" << endl;
4}else{
5cout << "托管的空间还在" << endl;
6}
删除器是C++智能指针的一个重要组成部分,用于定义当智能指针销毁时如何释放所管理的资源。
删除器(Deleter)是一个可调用对象(函数、函数对象、lambda表达式等),用于指定智能指针在销毁时如何释放资源。不同的智能指针对删除器的支持程度不同:
std::unique_ptr
:支持自定义删除器
std::shared_ptr
:支持自定义删除器
std::weak_ptr
:不直接管理资源,无删除器
std::unique_ptr
的默认删除器是std::default_delete
,对单个对象使用delete
,对数组使用delete[]
。
xxxxxxxxxx
121
2
3
4int main() {
5 // 默认删除器,等价于 std::unique_ptr<int, std::default_delete<int>>
6 std::unique_ptr<int> ptr1(new int(42));
7
8 // 数组的默认删除器
9 std::unique_ptr<int[]> ptr2(new int[10]);
10
11 return 0;
12} // ptr1和ptr2在此处自动销毁,调用相应的删除器
自定义删除器
1. 函数指针作为删除器
xxxxxxxxxx
271
2
3
4
5// 自定义删除函数
6void customDelete(int* ptr) {
7 std::cout << "Custom deleting: " << *ptr << std::endl;
8 delete ptr;
9}
10
11// FILE*的删除器
12void fileDeleter(FILE* file) {
13 if (file) {
14 std::cout << "Closing file" << std::endl;
15 fclose(file);
16 }
17}
18
19int main() {
20 // 使用函数指针作为删除器
21 std::unique_ptr<int, void(*)(int*)> ptr1(new int(42), customDelete);
22
23 // 管理FILE*资源
24 std::unique_ptr<FILE, void(*)(FILE*)> filePtr(fopen("test.txt", "w"), fileDeleter);
25
26 return 0;
27}
2. 函数对象作为删除器
xxxxxxxxxx
291
2
3
4// 函数对象删除器
5struct CustomDeleter {
6 void operator()(int* ptr) const {
7 std::cout << "Function object deleting: " << *ptr << std::endl;
8 delete ptr;
9 }
10};
11
12// 模板化的删除器
13template<typename T>
14struct ArrayDeleter {
15 void operator()(T* ptr) const {
16 std::cout << "Deleting array" << std::endl;
17 delete[] ptr;
18 }
19};
20
21int main() {
22 // 使用函数对象
23 std::unique_ptr<int, CustomDeleter> ptr1(new int(42));
24
25 // 使用模板删除器
26 std::unique_ptr<int, ArrayDeleter<int>> ptr2(new int[10]);
27
28 return 0;
29}
3. Lambda表达式作为删除器
xxxxxxxxxx
231
2
3
4int main() {
5 // Lambda删除器
6 auto deleter = [](int* ptr) {
7 std::cout << "Lambda deleting: " << *ptr << std::endl;
8 delete ptr;
9 };
10
11 std::unique_ptr<int, decltype(deleter)> ptr(new int(42), deleter);
12
13 // 直接在构造时使用lambda
14 auto ptr2 = std::unique_ptr<int, void(*)(int*)>(
15 new int(100),
16 [](int* p) {
17 std::cout << "Direct lambda: " << *p << std::endl;
18 delete p;
19 }
20 );
21
22 return 0;
23}
std::shared_ptr
的删除器更加灵活,可以在运行时指定,且不影响类型。
xxxxxxxxxx
241
2
3
4int main() {
5 // 默认删除器
6 std::shared_ptr<int> ptr1(new int(42));
7
8 // 自定义删除器
9 std::shared_ptr<int> ptr2(new int(100), [](int* p) {
10 std::cout << "Custom deleting: " << *p << std::endl;
11 delete p;
12 });
13
14 // 数组删除器
15 std::shared_ptr<int> ptr3(new int[10], [](int* p) {
16 std::cout << "Deleting array" << std::endl;
17 delete[] p;
18 });
19
20 // 或者使用std::default_delete
21 std::shared_ptr<int> ptr4(new int[5], std::default_delete<int[]>());
22
23 return 0;
24}
管理非内存资源
xxxxxxxxxx
511
2
3
4
5// 管理FILE*
6std::shared_ptr<FILE> openFile(const char* filename, const char* mode) {
7 FILE* file = fopen(filename, mode);
8 if (!file) {
9 return nullptr;
10 }
11
12 return std::shared_ptr<FILE>(file, [](FILE* f) {
13 if (f) {
14 std::cout << "Closing file" << std::endl;
15 fclose(f);
16 }
17 });
18}
19
20// 管理自定义资源
21class Resource {
22public:
23 Resource(int id) : id_(id) {
24 std::cout << "Resource " << id_ << " created" << std::endl;
25 }
26
27 ~Resource() {
28 std::cout << "Resource " << id_ << " destroyed" << std::endl;
29 }
30
31 int getId() const { return id_; }
32
33private:
34 int id_;
35};
36
37int main() {
38 // 管理文件
39 auto file = openFile("test.txt", "w");
40 if (file) {
41 fprintf(file.get(), "Hello, World!\n");
42 }
43
44 // 管理自定义资源
45 std::shared_ptr<Resource> res(new Resource(1), [](Resource* r) {
46 std::cout << "Custom deleting resource " << r->getId() << std::endl;
47 delete r;
48 });
49
50 return 0;
51}
1. 空删除器(不删除)
xxxxxxxxxx
161
2
3
4int main() {
5 int value = 42;
6
7 // 空删除器,不执行任何删除操作
8 std::shared_ptr<int> ptr(&value, [](int*) {
9 std::cout << "No-op deleter called" << std::endl;
10 // 不执行delete,因为value是栈上对象
11 });
12
13 std::cout << "Value: " << *ptr << std::endl;
14
15 return 0;
16}
2. 条件删除器
xxxxxxxxxx
261
2
3
4class ConditionalDeleter {
5public:
6 ConditionalDeleter(bool shouldDelete) : shouldDelete_(shouldDelete) {}
7
8 void operator()(int* ptr) const {
9 if (shouldDelete_) {
10 std::cout << "Deleting: " << *ptr << std::endl;
11 delete ptr;
12 } else {
13 std::cout << "Not deleting: " << *ptr << std::endl;
14 }
15 }
16
17private:
18 bool shouldDelete_;
19};
20
21int main() {
22 std::unique_ptr<int, ConditionalDeleter> ptr1(new int(42), ConditionalDeleter(true));
23 std::unique_ptr<int, ConditionalDeleter> ptr2(new int(100), ConditionalDeleter(false));
24
25 return 0;
26}
3. 统计删除器
xxxxxxxxxx
281
2
3
4
5class CountingDeleter {
6public:
7 void operator()(int* ptr) const {
8 ++deleteCount;
9 std::cout << "Delete #" << deleteCount << ": " << *ptr << std::endl;
10 delete ptr;
11 }
12
13 static std::atomic<int> deleteCount;
14};
15
16std::atomic<int> CountingDeleter::deleteCount{0};
17
18int main() {
19 {
20 std::unique_ptr<int, CountingDeleter> ptr1(new int(1));
21 std::unique_ptr<int, CountingDeleter> ptr2(new int(2));
22 std::unique_ptr<int, CountingDeleter> ptr3(new int(3));
23 } // 三个对象在此处被删除
24
25 std::cout << "Total deletions: " << CountingDeleter::deleteCount << std::endl;
26
27 return 0;
28}
make_unique的限制
xxxxxxxxxx
131
2
3
4int main() {
5 // make_unique不支持自定义删除器
6 // auto ptr1 = std::make_unique<int, CustomDeleter>(42); // 编译错误
7
8 // 必须直接构造
9 auto deleter = [](int* p) { delete p; };
10 std::unique_ptr<int, decltype(deleter)> ptr(new int(42), deleter);
11
12 return 0;
13}
make_shared的限制
xxxxxxxxxx
151
2
3
4int main() {
5 // make_shared也不支持自定义删除器
6 // auto ptr1 = std::make_shared<int>(42, customDeleter); // 编译错误
7
8 // 必须直接构造
9 std::shared_ptr<int> ptr(new int(42), [](int* p) {
10 std::cout << "Custom deleting" << std::endl;
11 delete p;
12 });
13
14 return 0;
15}
Caution
智能指针被误用,原因都是将一个原生裸指针交给了不同的智能指针进行托管,而造成尝试对一个对象销毁两次。