很多初学者觉得正确的值引用晦涩难懂,其实不然。右值引用只是一种新的C语法。真正难以理解的是基于右值引用的两个C编程技巧,即移动语义和完美转发。本节向读者解释什么是正确的值引用及其基本用法。
目录
移动语义完美转发概述
概述
在C中,常量、变量或表达式必须是左值(lvalue)或右值(rvalue)。
左值:非临时(已命名,可在多个语句中使用,可寻址)。可以出现在等号的左边或右边。可分为非常左值和常左值。
右值:temporary(匿名,只在当前语句中有效,不能带地址)。只能出现在等号的右边。可分为非常右值和常右值。
左值引用:对左值的引用是左值引用。可分为非恒定左值参考和恒定左值参考。
注意:常量左值引用是一种“通用”的引用类型,可以绑定所有类型的值,包括非常量左值、常量左值、非常量右值和常量右值。
右值引用:对右值的引用就是右值引用。可分为非恒定右值参考和恒定右值参考。
作为临时对象的右值,它的生命周期很短,一般是在当前表达式执行完之后才释放。
将它赋给正确的值引用,就可以“更新”它,而不需要昂贵的复制操作,它的生命周期和正确的值引用类型变量一样长。
右值指称的两个基本特征:移动语义和完美转发。
移动语义(Move Semantics)
您可以将资源从一个对象转移到另一个对象;主要解决减少不必要的临时对象的创建、复制和销毁的问题。
移动构造函数MyClass(类型a):当构造函数参数为右值时,移动构造函数优先于复制构造函数MyClass(常量类型a)。
移动赋值运算符类型operator=(类型a):当赋值为右值时,移动赋值运算符类型operator=(常量类型a)是首选。
#包括iostream
#包含字符串
#包含实用程序
结构MyClass
{
STD:string s;
MyClass(const char* sz) : s(sz)
{
STD:cout ' my class SZ:' SZ STD:endl;
}
MyClass(const MyClass o) : s(o.s)
{
std:cout '复制构造!\ n ';
}
my class(my class o)no exception:s(STD:move(o . s))
{
std:cout 'move构造!\ n ';
}
my class operator=(const my class other){//复制赋值
std:cout '复制赋值!\ n ';
s=other.s
返回* this
}
MyClass运算符=(my class other)no except {//move assign
std:cout '移动赋值!\ n ';
s=STD:move(other . s);
返回* this
}
静态MyClass GetMyClassGo(const char * SZ)
{
my class o(SZ);//注意:可能会被NRVO优化。
返回o;
}
};
void func0(MyClass o)
{
STD:cout o . s . c _ str()STD:endl;
}
void func1(MyClass o)
{
STD:cout o . s . c _ str()STD:endl;
}
void func2(const MyClass o)
{
STD:cout o . s . c _ str()STD:endl;
}
void func3(MyClass o)
{
STD:cout o . s . c _ str()STD:endl;
}
int main(int arg,char* argv[])
{
my class a1(' how ');
my class a2(' are ');
a2=a1//copy assign注意:a1是左值。
a2=my class(' you ');//move assign注意:MyClass('you ')是右值。
my class a3(a1);//复制构造注意:a1是左值。
my class a4=my class:GetMyClassGo(' go ');//move构造注意:它发生在MyClass:GetMyClassGo()内部
my class a5=my class:GetMyClassGo(' China ');//移动构造注释两次:一次发生在MyClass:GetMyClassGo()内部;另一次发生在返回值赋给a5的时候。
my class a6(' let ');
my class a7(' it ');
my class A8(' go ');
MyClass a9('!');
func 0(a6);//复制构造
func 1(a7);
func 2(A8);
//func 3(a9);//编译错误:不能将左值赋给右值。
func 0(my class:GetMyClassGo(' god '));//移动构造注释两次:一次发生在MyClass:GetMyClassGo()内部;另一种情况是将返回值赋给foo0参数。
//func 1(my class:GetMyClassGo(' is '));//编译错误:不能将右值赋给左值。
func 2(my class:GetMyClassGo(' girl '));//move构造注意:它发生在MyClass:GetMyClassGo()内部
func3(MyClass:GetMyClassGo('!'));//move构造注意:它发生在MyClass:GetMyClassGo()内部
返回0;
}
注意:要测试上述代码,您必须关闭C编译器优化技术- RVO,NRVO和复制省略。
使用std:move实现移动语义。
将左值或右值强制转换为右值引用。注意:UE4对应的是MoveTemp模板函数。
Std:move(en chs)不移动任何东西,它只是将一个对象的状态或所有权从一个对象转移到另一个对象。注意:只是转移,没有内存重定位或者内存复制。
基本类型后(如int、double等。)被std:move移动,其值不会改变。
复合类型被std:move移动后,处于未定义但有效的状态(大部分成员函数还是有意义的)。例如,标准库中的容器类对象被移动后,它将成为一个空容器。
完美转发(Perfect Forwarding)
对于模板函数,使用通用引用将一组参数原封不动地传递给另一个函数。
不变的意思是:左值,右值,是否为const不变。带来以下三个好处:
确保左值和右值的属性。
避免不必要的复印操作。
避免模板函数需要左值、右值或const参数来实现不同的重载。
通用引用(转发引用)是一种特殊类型的模板引用,它采用右值引用的语法形式(但不是右值引用)。例如模板类T void func(T t) {}
t当自动类型推断发生时,它是一个未确定的通用引用类型,t取决于传递的参数t是右还是左。右值通过T变成右值引用,左值通过T变成左值引用。
Std:move是用万能引用实现的。其定义如下:
模板类型名T
typename remove _ reference:type move(T T)
{
返回static _ cast typename remove _ reference:type(t);
}
/*****************************************
函数的作用是从类型中移除引用。
STD:remove _ reference et:type-T
STD:remove _ reference et:type-T
STD:remove _ reference et:type-T
******************************************/
//最原始、最常见的版本
模板typename T struct remove _ reference {
typedef T类型;//将T的类型别名定义为type
};
//有些版本比较特殊,会用于左值引用和右值引用。
template t struct remove _ reference t//左值引用
{ typedef T类型;}
template tstruct remove _ reference//右值引用
{ typedef T类型;}
当t为左值时,展开为:U move(U t)注意:右值指的是类型变量,也是左值。
当t为右值时,展开为:U move(U t)
最后,通过static_cast强制类型转换返回正确的值引用。注意:static_cast之所以可以使用类型转换,是为了通过remove _ reference:type:type模板移除t和t的引用,从而获得特定的类型t(模板特化)。
报价折叠
规则:具有左值的引用是具有左值的引用,否则是具有右值的引用。
使用std:forward实现参数的完美转发。其定义如下(en chs):
模板类型名T
T forward(remove_reference_tT参数)//将左值作为左值或右值转发
{
返回static _ castT(arg);
}
模板类型名T
T forward(remove_reference_tT参数)//将右值作为右值转发
{
static_assert(!is_lvalue_reference_vT,'错误的前向调用');
返回static _ castT(arg);
}
最后通过static_cast对引用进行折叠,经过强制类型转换,实现了原封不动的转发参数。注:UE4对应正向模板功能
空栏(int a,int b)
{
int c=a b;
}
void func(int a,int b)
{
int c=a b;
}
模板类型名A,类型名B
Void foo(A a,B b) {//a,B是左值引用或右值引用。
bar(std:forwardA(a),STD:forward b(b));//STD:forward转发前后,参数A和B的类型完全不变。
}
int main(int arg,char* argv[])
{
int a=10
foo(a,20);//扩展到void foo(int a,int b)。std:forward完美转发后,会调用void bar(int a,int b)函数。
func(std:forwardint(a),STD:forward int(30));//STD:forward完美转发后,会调用void func(int a,int b)函数。
返回0;
}
以上是C右值参考的详细内容。更多关于C右值参考的信息,请关注我们的其他相关文章!
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。