C++高级特性——左值、右值,移动语义,完美转发,RVO。
· 阅读需 9 分钟
本文章总结了C++中的左值与右值、左右值引用、移动语义、完美转发以及返回值优化(RVO)的概念和应用。
前言
过去在学校上了高级程序语言设计(C),面向对象程序设计以及数据结构与算法分析,学到了C++的基础,但随着使用的深入(如,Unity&&Unreal底层逻辑),等等,对C++提出了更多的挑战,尝试学习的时候才发现原来C++还有那么多表面上看不到的功能,高级特性也几乎没接触过、果然学校的课程还是邋遢 ;
当然,这仅仅是学习记录,这些知识已经是几个月前学的了,欢迎交流
内容
左值与右值
- 左值:持久存在,即当前语句结束后依然存在。
- 右值:表达 式结束后不再存在。 左值应当容易理解,给出一个右值的例子。
int main() {
int x = 10; // x是左值
int y = x + 5; // x + 5 是右值
int *p = &(x + 5); // 不能取右值的地址
cout << y << endl; // 输出15
return 0;
}
纯右值和将亡值
- 纯右值:表达式产生的中间值,不能取地址。
- 将亡值:将要被转移的右值。
一般,我们不必刻意区分一个右值到底是纯右值还是将亡值,甚至不需要去关注它。
auto c = std::move(a); // c是将亡值
auto d = static_cast<A&&>(a); // d是将亡值
左右值引用
std::move
使用std::move
函数强制把左值转换为右值。
int main() {
int a = 4;
// int &&b = a; // 报错;左值不允许赋给右值引用。
int &&b = std::move(a);
cout << b << endl;
cout << &b << endl;
cout << a << endl;
cout << &a << endl;
}
&&
是右值引用符号,只能是右值,而a
是左值,可以使用std::move()
将左值转为右值进行赋值。右值引用后指向的右值,会跟随引用持久存在,即将一个临时变量持久化。
class A {
public:
int a;
};
A getTemp() {
return A();
}
int main() {
int a = 10;
int& refA = a; // 左值引用
// int& ref2 = 2; // 编译错误
int&& ref1 = 1; // 右值引用
int b = 5;
// int&& refB = b; // 编译错误,不能将一个左值复制给一个右值引用
A&& refIns = getTemp(); // 函数返回值是右值
return 0;
}
在上面的代码中,getTemp
函数的返回值本应当销毁,但由于有右值引用指向它,因此不会立即销毁。
常量左值引用
常量左值引用可以绑定非常量左值、常量左值和右值,并延长右值的生命期,但只能读取,不能修改。
const A& a = getTemp(); // 不会报错
移动语义(转移语义)
正如名,移动某个语句,让其有新“含义”(指针); 移动语义即移动某个模块的所有权。若某个函数实现了自我移动构造函数:
class A {
public:
int size_;
int* data_;
A(const A &a) {
size_ = a.size_;
data_ = new int[size_];
cout << "copy " << endl;
}
A(A &&a) noexcept {
this->data_ = a.data_;
a.data_ = nullptr;
cout << "move " << endl;
}
};
A c = std::move(a);
在std::move
时,会进行浅拷贝,释放当前指针指向目标,然后指向新目标。移动构造函数与拷贝构造函数的区别是,拷贝构造的参数是const A&
,是常量左值引用,而移动构造的参数是A&&
,是右值引用,临时对象优先进入移动构造函数而不是拷贝构造函数。而移动构造函数与拷贝构造不同,它并不是重新分配一块新的空间,将要拷贝的对象复制过来,而是“偷”了过来,将自己的指针指向别人的资源,然后将别人的指针修改为nullptr
。
这样做,效率高于拷贝函数。
class MiniString {
public:
char* m_data;
// 拷贝构造函数
MiniString(const MiniString &str) {
CCtor++;
m_data = new char[strlen(str.m_data) + 1];
strcpy(m_data, str.m_data);
}
// 移动构造函数
MiniString(MiniString &&str) noexcept : m_data(str.m_data) {
MCtor++;
str.m_data = nullptr;
}
// 拷贝赋值函数
MiniString& operator=(const MiniString &str) {
CAsgn++;
if (this == &str) // 避免自我赋值
return *this;
delete[] m_data;
m_data = new char[strlen(str.m_data) + 1];
strcpy(m_data, str.m_data);
return *this;
}
// 移动赋值函数
MiniString& operator=(MiniString &&str) noexcept {
MAsgn++;
if (this == &str) // 避免自我赋值
return *this;
delete[] m_data;
m_data = str.m_data;
str.m_data = nullptr; // 不再指向之前的资源了
return *this;
}
};
int main()
{
vector<MiniString> vecStr;
vecStr.reserve(1000); //先分配好1000个空间,调用的次数可能远大于1000
for (int i = 0; i < 1000; i++) {
vecStr.push_back(MiniString("hello"));
}
cout << MiniString::CCtor << endl;
}