大纲
C++ 面向对象进阶
左值、右值、左值引用、右值引用、移动语义
- 左值:具有持久身份(可取地址)的表达式,通常表示一个在内存中有确定位置、可以被反复使用的对象。
- 右值:通常表示临时对象或纯值的表达式,一般不可取地址,生命周期短,主要用于赋值或初始化。
- 左值引用:使用
T& 声明,只能绑定到左值,本质上是某个已有对象的别名。 - 右值引用:使用
T&& 声明,只能绑定到右值,用于支持移动语义(move)和完美转发(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
|
临时对象深入探讨、解析、性能优化手段
产生临时对象的案例一
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()
|
特别注意
上面的代码运行后,可能会出现不同的结果,这是因为 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
| #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++ 编译器优化导致的,具体来说是返回值优化(Return Value Optimization,RVO)或命名返回值优化(NRVO)。从 C++ 17 开始,在某些返回值场景(如 return T(...) 或 return local_object)中,标准规定必须进行强制拷贝消除(Guaranteed Copy Elision),对象会直接在调用者的存储位置构造,而不会产生临时对象,因此即使关闭编译器优化(如 g++ main.cpp -fno-elide-constructors),也不会调用拷贝构造函数或移动构造函数,从而使程序的运行效率更高。
对象移动、移动构造函数、移动赋值运算符