-
左值:可以取地址并且有名字的东西就是左值。或者可以放到等号左边的东西叫左值。
-
右值:不能取地址的没有名字的东西就是右值。或者不可以放到等号左边的东西就叫右值。
-
纯右值:运算表达式产生的临时变量、不和对象关联的原始字面量、非引用返回的函数返回临时变量、lambda表达式等都是纯右值。
-
将亡值:可以理解为即将要销毁的值。
-
左值引用:对左值进行引用的类型。
-
右值引用:对右值进行引用的类型。
-
移动语义:转移资源所有权,类似于转让或者资源窃取的意思,对于那块资源,转为自己所拥有,别人不再拥有也不会再使用。
-
完美转发:可以写一个接受任意实参的函数模板,并转发到其它函数,目标函数会收到与转发函数完全相同的实参。
-
返回值优化:当函数需要返回一个对象实例时候,就会创建一个临时对象并通过复制构造函数将目标对象复制到临时对象,这里有复制构造函数和析构函数会被多余的调用到,有代价,而通过返回值优化,C++标准允许省略调用这些复制构造函数
左值一般有:
-
函数名和变量名
-
返回左值引用的函数调用
-
前置自增自减表达式++i、--i
-
由赋值表达式或赋值运算符连接的表达式(a=b, a += b等)
-
解引用表达式*p
-
字符串字面值"abcd"
右值可以赋给左值。
纯右值、将亡值
纯右值和将亡值都属于右值。
右值可以赋给左值。
纯右值
运算表达式产生的临时变量、不和对象关联的原始字面量、非引用返回的函数返回临时变量、lambda表达式等都是纯右值。
举例:
-
除字符串字面值外的字面值
-
返回非引用类型的函数调用
-
后置自增自减表达式i++、i--
-
算术表达式(a+b, a*b, a&&b, a==b等)
-
取地址表达式等(&a)
将亡值
将亡值是指C++11新增的和右值引用相关的表达式,通常指将要被移动的对象、T&&函数的返回值、std::move函数的返回值、转换为T&&类型转换函数的返回值,将亡值可以理解为即将要销毁的值,通过“盗取”其它变量内存空间方式获取的值,在确保其它变量不再被使用或者即将被销毁时,可以避免内存空间的释放和分配,延长变量值的生命周期,常用来完成移动构造或者移动赋值的特殊任务。
A a;
auto c = std::move(a);
auto d = static_cast<A&&>(a);
左值引用、右值引用
左值引用就是对左值进行引用的类型,右值引用就是对右值进行引用的类型,他们都是引用,都是对象的一个别名,并不拥有所绑定对象的堆存,所以都必须立即初始化。
type &name = exp; // 左值引用
type &&name = exp; // 右值引用
左值引用:
等号右边的值必须可以取地址,如果不能取地址,则会编译失败;
或者可以使用const引用形式,但这样就只能通过引用来读取输出,不能修改数组,因为是常量引用。
如:
int &a = 1; //不行
int &b = funct(); //不行
const int &c = funct();
右值引用:
通常情况下,右值引用是不能绑定到任何的左值的。
右值不存在名称,只能通过引用找到它的存在。
如: int && a = funct();
那表达式等号右边的值需要时右值,可以使用std::move函数强制把左值转换为右值。
int a = 4;
int &&b = a; // error, a是左值 int &&c = std::move(a); // 可以
右值存在的最大价值就是移动语义,另外一个价值是用于转发 左值引用的版本是一个拷贝构造版本,右值引用版本是一个移动构造版本。 编译器会隐式生成一个移动构造函数。如果声明了自定义的拷贝构造函数、拷贝赋值函数、析构函数中的一个或多个,编译器就不会生成默认版本。
拷贝构造/赋值 和移动构造/赋值函数必须同时提供,或者同时不提供。才能保证类同时具备拷贝和移动语义。只声明其中一种的话,类仅能实现一种语义。
具体的拷贝构造和移动构造函数见:
c++ 类与默认函数、包括构造函数和析构函数的特点_小飞侠hello的博客-CSDN博客
移动语义
可以理解为转移所有权,之前的拷贝是对于别人的资源,自己重新分配一块内存存储复制过来的资源,而对于移动语义,类似于转让或者资源窃取的意思,对于那块资源,转为自己所拥有,别人不再拥有也不会再使用。
std::move 基本等同于一个类型转换 static_cast<T&&> (lvalue);而static_cast<T&&> (lvalue)调用移动构造函数。std::move既可以传递一个左值,也可以传递一个右值。
template<class _Ty>
_NODISCARD constexpr remove_reference_t<_Ty>&&
move(_Ty&& _Arg) noexcept
{ // forward _Arg as movable
return (static_cast<remove_reference_t<_Ty>&&>(_Arg));
}
移动语义一定是要修改临时变量的值。
std::vector<string> vecs;
vecs.push_back("1");
vecs.push_back("2");
std::vector<string> vecm = std::move(vecs); // 免去很多拷贝,右值赋给左值
结果是 vecs 的大小为0
怎么利用移动语义呢,是通过移动构造函数
A(A&& a) {
this->data_ = a.data_;
a.data_ = nullptr;
cout << "move " << endl;
}
~A() {
if (data_ != nullptr) {
delete[] data_;
}
}
从拷贝、移动延伸到智能指针这类资源型的类型。
完美转发
完美转发指可以写一个接受任意实参的函数模板,并转发到其它函数,目标函数会收到与转发函数完全相同的实参,转发函数实参是左值那目标函数实参也是左值,转发函数实参是右值那目标函数实参也是右值。那如何实现完美转发呢,答案是使用std::forward()。
也是基于static_cast<T&&> (lvalue);的原理。forward和move差别并不大。
其使用场景包括:make_pair、 make_unique
背景:
规则1(引用折叠规则):如果间接的创建一个引用的引用,则这些引用就会“折叠”。在所有情况下(除了一个例外),引用折叠成一个普通的左值引用类型。一种特殊情况下,引用会折叠成右值引用,即右值引用的右值引用, T&& &&。即
- X& &、X& &&、X&& &都折叠成X&
- X&& &&折叠为X&&
规则2(右值引用的特殊类型推断规则):当将一个左值传递给一个参数是右值引用的函数,且此右值引用指向模板类型参数(T&&)时,编译器推断模板参数类型为实参的左值引用,如
template<typename T>
void f(T&&);
int i = 42;
f(i)
上述的模板参数类型T将推断为int&类型,而非int。
从上述两个规则可以得出结论:如果一个函数形参是一个指向模板类型的右值引用,则该参数可以被绑定到一个左值上,
规则3:虽然不能隐式的将一个左值转换为右值引用,但是可以通过static_cast显示地将一个左值转换为一个右值。【C++11中为static_cast新增的转换功能】。
重点剖析:
1.对于函数模板中使用右值引用语法定义的参数来说,它不再遵守这一规定,既可以接收右值,也可以接收左值(此时的右值引用又被称为“万能引用”)。
template<typename T>
void function(T &&t) //既可以接受左值,又可以接受右值
{
otherdef(t); // t继续传参,在otherdef()中又变成了左值
}
2.如何将函数模板接收到的形参连同其左、右值属性,一起传递给被调用的函数?
C++11引入了一个模板函数 forword() 。
//实现完美转发的函数模板
void otherdef(int &t);
void otherdef(int &&t);
template <typename T>
void forwordfunction(T&& t);
void newc11::otherdef(int &t)
{
qDebug() << "lvalue reference";
}
void newc11::otherdef(int &&t)
{
qDebug() << "rvalue reference";
}
template <typename T>
void newc11::forwordfunction(T&& t)
{
otherdef(forward<T>(t)); //将函数模板接收到的形参连同其左、右值属性,一起传递给被调用的函数
}
forwordfunction(1); //rvalue reference
int forword = 1;
forwordfunction(forword); //lvalue reference
这种方式只适用于函数模板,不适用类模板。
template<typename T>
class A
{
void fun(T && t); //这里是右值引用
};
T&&并不是万能引用,因为T的类型在模板实例化时已经确定,当实例函数void fun(T && t);时 T的类型已经确定
返回值优化(RVO)
是一种C++编译优化技术,当函数需要返回一个对象实例时候,就会创建一个临时对象并通过复制构造函数将目标对象复制到临时对象,这里有复制构造函数和析构函数会被多余的调用到,有代价,而通过返回值优化,C++标准允许省略调用这些复制构造函数。
根据effective modern c++中介绍,编译器进行RVO条件有二
- return 的值类型与 函数签名的返回值类型相同
- return的是一个局部对象
std::vector<int> return_vector(void) {
std::vector<int> tmp {1,2,3,4,5};
return tmp;
}
std::vector<int> rval_ref = return_vector();