C++三十二讲之第三讲

你好旅行者!欢迎来到cyt的练功房。本篇文章来记录一下为某个憨批讲解一下C++ 第三讲。主要讲的是我理解下的C++ 的移动语义

03 | 右值和移动究竟解决了什么问题?

一、什么是右值

在C11之前没有啥右值的和右值引用的概念,只有左值和左值引用,这是C11新引入的相关概念。在我的理解中右值与右值引用是为了延长变量的生命周期与减少拷贝或构造的开销的。左值右值主要是用来定义表达式的值的。可惜, C++ 11并没有明确定义左值和右值的概念,在C++ 17中才明确有了概念。
一个右值可以通过如下代码构造

1
int &&a = 5;

请记得,左值不可以赋给右值右值引用 , 但是右值和右值引用可以赋值给左值,不可以赋给左值引用

一个右值又可以分为纯右值和将亡值,左值没有这种分法,纯右值是如上面的&&a这样的值

将亡值是某一返回右值引用的表达式的值类别,如下

1
std::move(t)

纯右值就是上文中的尚未快被销毁的值a

说实话,好像C++17中还有一种临时变量化的将亡值方法,但是我记不太清了,就不误人子弟了反正主要是进行了编译时优化,强制使用复制消除。

二、右值的意义

右值主要用于减少大对象拷贝时的开销,可以将一个对象的所有权带给另一个变量。可以延长一个变量的生命周期。
在C++11之前的年代,有一些语法是十分不推荐的

1
string result = string("Hello, ") + name + ".";

调用构造函数 string(const char*),生成临时对象 1;"Hello, " 复制1次。

调用 operator+(const string&, const string&),生成临时对象 2 "Hello, " 复制 2 次,name 复制 1 次。

调用 operator+(const string&, const char*),生成对象 3;“Hello, " 复制 3 次,name 复制 2 次,”." 复制 1 次。

假设返回值优化能够生效(最佳情况),对象 3 可以直接在 result 里构造完成。

临时对象 2 析构,释放指向 string("Hello, ") + name 的内存。

临时对象 1 析构,释放指向 string("Hello, ") 的内存。

所以在之前的代码中,我们更习惯于写

1
2
string result = "Hello ,";
result += "///";

这样可以每个字符串只构造一次,如果将字符串改成一个比较大的类,是不是就节省了很多的开销呢?

但是在C++11之后,这就完全无所谓了,其会调用移动构造函数,直接将对象移动,不会产生复制,就不会有无用的内存块被析构了

三、万能引用与完美转发

当右值引用碰上模板时,情况又会发生变化

1
2
3
4
template<typename T>
void test_left(T&& a){
std::cout<<typeid(a).name()<<std::endl;
}

当传入的a是左值或者左值引用时,a将变为左值引用,当传入的是右值或右值引用时,a将变成右值引用。可以通过上面这段代码实践一下。

完美转发是因为在传入右值时,其本身变为了一个左值,进行了构造,如果我们想返回一个右值的话就需要对其进行类型的转换

1
2
3
4
5
6
template <typename T>
void bar(T&& s)
{
foo(std::forward<T>(s));
}

在上面这个函数中,就用到了完美转发,在右值被传入这个函数之后,虽然之前的值是一个右值,但是s变成了左值,要想返回一个右值类型的变量怎么办呢?forward实现如下

1
2
3
4
5
6
7
8
template<typename T>
T&& forward(T&& s){
return static_cast<T&&>(s);
}
template<typename T>
T&& forward(T& s){
return static_cast<T&&>(s);
}

这样就可以保证返回的是值的正确类型了

使用搜索:谷歌必应百度