大纲 C++ 面向对象进阶 左值、右值、左值引用、右值引用、移动语义 左值引用、右值引用的概念 左值与右值的概念
左值:具有持久身份(可取地址)的表达式,通常表示一个在内存中有确定位置、可以被反复使用的对象。 右值:通常表示临时对象或纯值的表达式,一般不可取地址,生命周期短,主要用于赋值或初始化。 左值引用与右值引用的概念
左值引用:使用 T& 声明,只能绑定到左值,本质上是某个已有对象的别名。 右值引用:使用 T&& 声明,只能绑定到右值,用于支持移动语义(move)和完美转发(forward)。 左值引用与右值引用的区别
左值引用和右值引用的主要区别在于它们可以绑定的值类别,左值引用只能绑定到左值,而右值引用只能绑定到右值。 右值引用引入了 move 移动语义,使得 C++ 可以更高效地处理临时对象。 在泛型编程中,可以通过函数模板的类型推导来同时处理左值引用和右值引用,从而实现参数的 forward 完美转发。 左值和右值的总结
左值:可以出现在赋值符号 = 的左边,本质是 "有地址的对象"。 右值:可以出现在赋值符号 = 的右边,通常是临时值或纯值。 判断规则:能取地址的一般是左值,不能取地址的一般是右值。 特殊情况:在特定场景下,有些左值也可以被当作右值使用,比如 int i = 20; i = i + 10;。 左值引用、右值引用的使用 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 #include <iostream> using namespace std;void test01 () { int value = 10 ; int &refval = value; refval = 20 ; const int &refval2 = value; const int &refval3 = 3 ; int &&refval4 = 30 ; refval4 = 35 ; } void test02 () { int i = 1 ; int &refval = ++i; int &&refval2 = i++; }
移动语义 std::move () 的使用 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 #include <iostream> void test01 () { int i = 10 ; int &&refval = std::move (i); i = 20 ; std::cout << "i = " << i << ", refval = " << refval << std::endl; refval = 25 ; std::cout << "i = " << i << ", refval = " << refval << std::endl; } void test02 () { int &&refval = 10 ; int &&refval2 = std::move (refval); refval = 30 ; std::cout << "refval = " << refval << ", refval2 = " << refval2 << std::endl; refval2 = 35 ; std::cout << "refval = " << refval << ", refval2 = " << refval2 << std::endl; } void test03 () { std::string str = "I Love China" ; std::string &&str2 = std::move (str); std::cout << "str = " << str << ", &str = " << &str << std::endl; std::cout << "str2 = " << str2 << ", &str2 = " << &str2 << std::endl; std::string str3 = std::move (str); std::cout << "str = " << str << ", &str = " << &str << std::endl; std::cout << "str3 = " << str3 << ", &str3 = " << &str3 << std::endl; } int main () { test01 (); test02 (); test03 (); return 0 ; }
程序运行输出的结果如下:
1 2 3 4 5 6 7 8 i = 20, refval = 20 i = 25, refval = 25 refval = 30, refval2 = 30 refval = 35, refval2 = 35 str = I Love China, &str = 0x7fffb7650ef0 str2 = I Love China, &str2 = 0x7fffb7650ef0 str = , &str = 0x7fffb7650ef0 str3 = I Love China, &str3 = 0x7fffb7650ed0
特别注意
在 C++ 中,std::move() 的作用是将一个左值强制转换为一个右值,这样就可以绑定到右值引用(比如 T &&t = std::move(obj);)。
临时对象深入探讨、解析、性能优化手段 特别注意
下面的案例代码运行后,可能会出现不同的结果,这是因为 C++ 编译器优化导致的,具体来说是返回值优化(Return Value Optimization,RVO)或命名返回值优化(NRVO)。从 C++ 17 开始,在某些返回值场景(如 return T(...) 或 return local_object)中,标准规定必须进行强制拷贝消除(Guaranteed Copy Elision),对象会直接在调用者的存储位置构造,而不会产生临时对象,因此即使关闭编译器优化(如 g++ main.cpp -fno-elide-constructors),也不会调用拷贝构造函数或移动构造函数,从而使程序的运行效率更高。
产生临时对象的案例一 后置 ++ 运算符,会产生临时对象
1 2 3 4 5 6 7 8 9 10 #include <iostream> int main () { int i = 1 ; int && ref1 = i++; ref1 += 5 ; std::cout << "i = " << i << ", ref1 = " << ref1 << std::endl; return 0 ; }
程序运行输出的结果如下:
产生临时对象的案例二 以传值的方式给函数传递参数,会产生临时对象
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 #include <iostream> class MyValue {public : MyValue (int v1 = 0 , int v2 = 0 ) : val1 (v1), val2 (v2) { std::cout << "MyValue()" << std::endl; } MyValue (const MyValue& mv) : val1 (mv.val1), val2 (mv.val2) { std::cout << "MyValue(const MyValue&)" << std::endl; } ~MyValue () { std::cout << "~MyValue()" << std::endl; } int sum1 (MyValue mv) { int tmp = mv.val1 + mv.val2; mv.val1 = 1000 ; return tmp; } int sum2 (MyValue& mv) { int tmp = mv.val1 + mv.val2; mv.val1 = 1000 ; return tmp; } public : int val1; int val2; }; void test01 () { std::cout << "======== test01() =======" << std::endl; MyValue mv (10 , 20 ) ; int sum = mv.sum1 (mv); std::cout << "sum = " << sum << std::endl; std::cout << "mv.val1 = " << mv.val1 << std::endl; } void test02 () { std::cout << "======== test02() =======" << std::endl; MyValue mv (30 , 40 ) ; int sum = mv.sum2 (mv); std::cout << "sum = " << sum << std::endl; std::cout << "mv.val1 = " << mv.val1 << std::endl; } int main () { test01 (); test02 (); return 0 ; }
程序运行输出的结果如下:
1 2 3 4 5 6 7 8 9 10 11 12 ======== test01() ======= MyValue() MyValue(const MyValue&) ~MyValue() sum = 30 mv.val1 = 10 ~MyValue() ======== test02() ======= MyValue() sum = 70 mv.val1 = 1000 ~MyValue()
产生临时对象的案例三 基础类型发生隐式类型转换,会产生临时对象
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 #include <iostream> int calc (const std::string& str, char ch) { int count = 0 ; for (const char & c : str) { if (ch == c) { count++; } } return count; } int main () { char str[50 ] = "I Love China, Yeah!" ; int count = calc (str, 'a' ); std::cout << "count = " << count << std::endl; std::string str2 = "I Love China, Yeah!" ; int count2 = calc (str, 'a' ); std::cout << "count2 = " << count2 << std::endl; return 0 ; }
程序运行输出的结果如下:
产生临时对象的案例四 构造函数发生隐式类型转换,会产生临时对象
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 #include <iostream> class MyValue {public : MyValue (int v1 = 0 , int v2 = 0 ) : val1 (v1), val2 (v2) { std::cout << "MyValue()" << std::endl; } MyValue (const MyValue& mv) : val1 (mv.val1), val2 (mv.val2) { std::cout << "MyValue(const MyValue&)" << std::endl; } MyValue& operator =(const MyValue& mv) { std::cout << "MyValue& operator=(const MyValue&)" << std::endl; val1 = mv.val1; val2 = mv.val2; return *this ; } ~MyValue () { std::cout << "~MyValue()" << std::endl; } public : int val1; int val2; }; void test01 () { std::cout << "======== test01() =======" << std::endl; MyValue mv; std::cout << "--------------" << std::endl; mv = 2000 ; std::cout << "--------------" << std::endl; } void test02 () { std::cout << "======== test02() =======" << std::endl; MyValue mv = 1000 ; } int main () { test01 (); test02 (); return 0 ; }
程序运行输出的结果如下:
1 2 3 4 5 6 7 8 9 10 11 ======== test01() ======= MyValue() -------------- MyValue() MyValue& operator=(const MyValue&) ~MyValue() -------------- ~MyValue() ======== test02() ======= MyValue() ~MyValue()
产生临时对象的案例五 函数将临时对象作为返回值,会产生临时对象
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 #include <iostream> class MyValue {public : MyValue (int v1 = 0 , int v2 = 0 ) : val1 (v1), val2 (v2) { std::cout << "MyValue()" << std::endl; } MyValue (const MyValue& mv) : val1 (mv.val1), val2 (mv.val2) { std::cout << "MyValue(const MyValue&)" << std::endl; } MyValue& operator =(const MyValue& mv) { std::cout << "MyValue& operator=(const MyValue&)" << std::endl; val1 = mv.val1; val2 = mv.val2; return *this ; } ~MyValue () { std::cout << "~MyValue()" << std::endl; } MyValue Double1 (MyValue& mv) { MyValue tmp; tmp.val1 = mv.val1 * 2 ; tmp.val2 = mv.val2 * 2 ; return tmp; } MyValue Double2 (MyValue& mv) { return MyValue (mv.val1 * 2 , mv.val2 * 2 ); } public : int val1; int val2; }; int main () { MyValue mv (10 , 20 ) ; std::cout << "------------" << std::endl; mv.Double1 (mv); std::cout << "------------" << std::endl; return 0 ; }
程序运行输出的结果如下(编译器没有进行返回值优化【RVO / NRVO】的情况下):
1 2 3 4 5 6 7 8 MyValue() ------------ MyValue() MyValue(const MyValue&) ~MyValue() ~MyValue() ------------ ~MyValue()
程序运行输出的结果如下(编译器进行了返回值优化【RVO / NRVO】的情况下):
1 2 3 4 5 6 MyValue() ------------ MyValue() ~MyValue() ------------ ~MyValue()
产生临时对象的案例六 类外实现运算符重载时,函数将临时对象作为返回值,会产生临时对象
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 #include <iostream> class MyValue {public : MyValue (int v1 = 0 , int v2 = 0 ) : val1 (v1), val2 (v2) { std::cout << "MyValue()" << std::endl; } MyValue (const MyValue& mv) : val1 (mv.val1), val2 (mv.val2) { std::cout << "MyValue(const MyValue&)" << std::endl; } ~MyValue () { std::cout << "~MyValue()" << std::endl; } public : int val1; int val2; }; MyValue operator +(MyValue& m1, MyValue& m2) { std::cout << "operator+()" << std::endl; MyValue result; result.val1 = m1.val1 + m2.val1; result.val2 = m1.val2 + m2.val2; return result; } MyValue operator -(MyValue& m1, MyValue& m2) { std::cout << "operator-()" << std::endl; return MyValue (m1.val1 - m2.val1, m1.val2 - m2.val2); } int main () { MyValue mv1 (10 , 20 ) ; MyValue mv2 (30 , 40 ) ; std::cout << "------------" << std::endl; mv1 + mv2; std::cout << "------------" << std::endl; return 0 ; }
程序运行输出的结果如下(编译器没有进行返回值优化【RVO / NRVO】的情况下):
1 2 3 4 5 6 7 8 9 10 11 MyValue() MyValue() ------------ operator+() MyValue() MyValue(const MyValue&) ~MyValue() ~MyValue() ------------ ~MyValue() ~MyValue()
程序运行输出的结果如下(编译器进行了返回值优化【RVO / NRVO】的情况下):
1 2 3 4 5 6 7 8 9 MyValue() MyValue() ------------ operator+() MyValue() ~MyValue() ------------ ~MyValue() ~MyValue()
对象移动、移动构造函数、移动赋值运算符 为了解决临时对象带来的性能问题,C++ 11 引入了 “对象移动” 的概念,并增加了移动构造函数和移动赋值运算符的支持。在 C++ 中,建议统一使用以下四个标准术语:
拷贝构造函数(Copy Constructor),比如:MyString(const MyString& str) { } 移动构造函数(Move Constructor),比如:MyString(MyString&& str) { } 拷贝赋值运算符(Copy Assignment Operator),比如:MyString& operator=(const MyString& str) { } 移动赋值运算符(Move Assignment Operator),比如:MyString& operator=(MyString&& str) { } 提示
上面介绍的拷贝赋值运算符(Copy Assignment Operator),其实就是平时常说的赋值运算符(带左值引用参数),比如:MyString& operator=(const MyString& str) { }。在 C++ 中,移动构造函数和移动赋值运算符并不是必须配合 std::move 一起使用,比如对于 MyString a = MyString("hello");,编译器会自动调用移动构造函数;函数返回值通常也不需要使用 std::move,因为编译器可能会直接消除拷贝(Copy Elision)或者调用移动构造函数;但当对象是左值时,则需要使用 std::move 将其转换为右值后,才能触发移动语义,这样编译器才会自动调用移动构造函数或者移动赋值运算符 。
特别注意
下面的案例代码运行后,可能会出现不同的结果,这是因为 C++ 编译器优化导致的,具体来说是返回值优化(Return Value Optimization,RVO)或命名返回值优化(NRVO)。从 C++ 17 开始,在某些返回值场景(如 return T(...) 或 return local_object)中,标准规定必须进行强制拷贝消除(Guaranteed Copy Elision),对象会直接在调用者的存储位置构造,而不会产生临时对象,因此即使关闭编译器优化(如 g++ main.cpp -fno-elide-constructors),也不会调用拷贝构造函数或移动构造函数,从而使程序的运行效率更高。
移动构造函数使用案例 不使用移动构造函数的案例代码
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 65 66 67 68 69 70 71 72 73 74 75 #include <iostream> class B {public : B (int bm = 0 ) : m_bm (bm) { std::cout << "B(int bm)" << std::endl; } B (const B& b) : m_bm (b.m_bm) { std::cout << "B(const B&)" << std::endl; } ~B () { std::cout << "~B()" << std::endl; } private : int m_bm; }; class A {public : A () : m_pb (new B ()) { std::cout << "A()" << std::endl; } A (const A& a) : m_pb (new B (*(a.m_pb))) { std::cout << "A(const A&)" << std::endl; } ~A () { std::cout << "~A()" << std::endl; delete m_pb; } private : B* m_pb; }; static A getA () { A tmp; return tmp; } void test01 () { std::cout << "======== test01() =======" << std::endl; B* pb = new B (); B* pb2 = new B (*pb); delete pb; delete pb2; } void test02 () { std::cout << "======== test02() =======" << std::endl; A a = getA (); } void test03 () { std::cout << "======== test03() =======" << std::endl; A a = getA (); A a2 = a; } int main () { test01 (); test02 (); test03 (); return 0 ; }
程序运行输出的结果如下(编译器没有进行返回值优化【RVO / NRVO】的情况下):
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 ======== test01() ======= B(int bm) B(const B&) ~B() ~B() ======== test02() ======= B(int bm) A() B(const B&) A(const A&) ~A() ~B() ~A() ~B() ======== test03() ======= B(int bm) A() B(const B&) A(const A&) ~A() ~B() B(const B&) A(const A&) ~A() ~B() ~A() ~B()
程序运行输出的结果如下(编译器进行了返回值优化【RVO / NRVO】的情况下):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 ======== test01() ======= B(int bm) B(const B&) ~B() ~B() ======== test02() ======= B(int bm) A() ~A() ~B() ======== test03() ======= B(int bm) A() B(const B&) A(const A&) ~A() ~B() ~A() ~B()
使用移动构造函数的案例代码
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 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 #include <iostream> class B {public : B (int bm = 0 ) : m_bm (bm) { std::cout << "B(int bm)" << std::endl; } B (const B& b) : m_bm (b.m_bm) { std::cout << "B(const B&)" << std::endl; } ~B () { std::cout << "~B()" << std::endl; } private : int m_bm; }; class A {public : A () : m_pb (new B ()) { std::cout << "A()" << std::endl; } A (A&& a) noexcept : m_pb (a.m_pb) { a.m_pb = nullptr ; std::cout << "A(A &&)" << std::endl; } A (const A& a) : m_pb (new B (*(a.m_pb))) { std::cout << "A(const A&)" << std::endl; } ~A () { std::cout << "~A()" << std::endl; delete m_pb; } private : B* m_pb; }; static A getA () { A tmp; return tmp; } void test01 () { std::cout << "======== test01() =======" << std::endl; B* pb = new B (); B* pb2 = new B (*pb); delete pb; delete pb2; } void test02 () { std::cout << "======== test02() =======" << std::endl; A a = getA (); } void test03 () { std::cout << "======== test03() =======" << std::endl; A a = getA (); A a2 = std::move (a); } int main () { test01 (); test02 (); test03 (); return 0 ; }
程序运行输出的结果如下(编译器没有进行返回值优化【RVO / NRVO】的情况下):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 ======== test01() ======= B(int bm) B(const B&) ~B() ~B() ======== test02() ======= B(int bm) A() A(A &&) ~A() ~A() ~B() ======== test03() ======= B(int bm) A() A(A &&) ~A() A(A &&) ~A() ~B() ~A()
程序运行输出的结果如下(编译器进行了返回值优化【RVO / NRVO】的情况下):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 ======== test01() ======= B(int bm) B(const B&) ~B() ~B() ======== test02() ======= B(int bm) A() ~A() ~B() ======== test03() ======= B(int bm) A() A(A &&) ~A() ~B() ~A()
移动赋值运算符使用案例 使用移动赋值运算符的案例代码
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 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 #include <iostream> class B {public : B (int bm = 0 ) : m_bm (bm) { std::cout << "B(int bm)" << std::endl; } B (const B& b) : m_bm (b.m_bm) { std::cout << "B(const B&)" << std::endl; } ~B () { std::cout << "~B()" << std::endl; } private : int m_bm; }; class A {public : A () : m_pb (new B ()) { std::cout << "A()" << std::endl; } A (const A& a) : m_pb (new B (*(a.m_pb))) { std::cout << "A(const A&)" << std::endl; } A& operator =(const A& a) { std::cout << "A& operator=(const A&)" << std::endl; if (this == &a) { return *this ; } if (m_pb != nullptr ) { delete m_pb; } m_pb = new B (*(a.m_pb)); return *this ; } A& operator =(A&& a) noexcept { std::cout << "A& operator=(A&&)" << std::endl; if (this == &a) { return *this ; } if (m_pb != nullptr ) { delete m_pb; } m_pb = a.m_pb; a.m_pb = nullptr ; return *this ; } ~A () { std::cout << "~A()" << std::endl; delete m_pb; } private : B* m_pb; }; void test01 () { std::cout << "======== test01() =======" << std::endl; A a; A a2; a2 = a; } void test02 () { std::cout << "======== test02() =======" << std::endl; A a; A a2; a2 = std::move (a); } int main () { test01 (); test02 (); return 0 ; }
程序运行输出的结果如下(编译器没有进行返回值优化【RVO / NRVO】的情况下):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 ======== test01() ======= B(int bm) A() B(int bm) A() A& operator=(const A&) ~B() B(const B&) ~A() ~B() ~A() ~B() ======== test02() ======= B(int bm) A() B(int bm) A() A& operator=(A&&) ~B() ~A() ~B() ~A()
程序运行输出的结果如下(编译器进行了返回值优化【RVO / NRVO】的情况下):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 ======== test01() ======= B(int bm) A() B(int bm) A() A& operator=(const A&) ~B() B(const B&) ~A() ~B() ~A() ~B() ======== test02() ======= B(int bm) A() B(int bm) A() A& operator=(A&&) ~B() ~A() ~B() ~A()
合成的移动操作的使用案例 总结
声明和定义移动构造函数和移动赋值运算符时,建议统一使用 noexcept 关键字修饰,以便 STL 容器在优化时能够优先选择移动操作。 如果类没有定义移动构造函数和移动赋值运算符,那么系统会在需要移动语义时,自动调用该类的拷贝构造函数或者拷贝赋值运算符进行替代。 对于涉及动态资源管理(如 malloc()、new、文件句柄等)的复杂类,建议增加移动构造函数和移动赋值运算符,以减少不必要的资源拷贝开销。 有些类是没有移动构造函数和移动赋值运算符的,因为它们可能定义了拷贝构造函数、拷贝赋值运算符或者析构函数,导致 C++ 编译器不会再自动生成移动操作。