无线播放电视的浏览器,云南网站建设优化,wordpress路由正则,淄博网站制作设计高端目录
一、右值引用概念
二、 左值与右值
三、引用与右值引用比较
四、值的形式返回对象的缺陷
五、移动语义
六、右值引用引用左值
七、完美转发
八、右值引用作用 一、右值引用概念 C98中提出了引用的概念#xff0c;引用即别名#xff0c;引用变量与其引用实体公共…目录
一、右值引用概念
二、 左值与右值
三、引用与右值引用比较
四、值的形式返回对象的缺陷
五、移动语义
六、右值引用引用左值
七、完美转发
八、右值引用作用 一、右值引用概念 C98中提出了引用的概念引用即别名引用变量与其引用实体公共同一块内存空间而引用的底层是通过指针来实现的因此使用引用可以提高程序的可读性。 void Swap(int left, int right)
{int temp left;left right;right temp;
}
int main()
{int a 10;int b 20;Swap(a, b);
}
为了提高程序运行效率C11中引入了右值引用右值引用也是别名但其只能对右值引用。
int Add(int a, int b)
{return a b;
}
int main()
{const int ra 10;// 引用函数返回值返回值是一个临时变量为右值int rRet Add(10, 20);return 0;
}
为了与C98中的引用进行区分C11将该种方式称之为右值引用。
二、 左值与右值 左值与右值是C语言中的概念但C标准并没有给出严格的区分方式一般认为可以放在左边的或者能够取地址的称为左值只能放在右边的或者不能取地址的称为右值但是也不一定完全正确。 int g_a 10;
// 函数的返回值结果为引用
int GetG_A()
{return g_a;
}
int main()
{int a 10;int b 20;// a和b都是左值b既可以在的左侧也可在右侧// 说明左值既可放在的左侧也可放在的右侧a b;b a;const int c 30;// 编译失败c为const常量只读不允许被修改//c a;// 因为可以对c取地址因此c严格来说不算是左值cout c endl;// 编译失败因为b1的结果是一个临时变量没有具体名称也不能取地址因此为右值//b 1 20;GetG_A() 100;return 0;
}
因此关于左值与右值的区分不是很好区分一般认为 1. 普通类型的变量因为有名字可以取地址都认为是左值。 2. const修饰的常量不可修改只读类型的理论应该按照右值对待但因为其可以取地址(如果只是const类型常量的定义编译器不给其开辟空间如果对该常量取地址时编译器才为其开辟空间)C11认为其是左值。 3. 如果表达式的运行结果是一个临时变量或者对象认为是右值。 4. 如果表达式运行结果或单个变量是一个引用则认为是左值。 总结 1. 不能简单地通过能否放在左侧右侧或者取地址来判断左值或者右值要根据表达式结果或变量的性质判断比如上述c常量 2. 能得到引用的表达式一定能够作为引用否则就用常引用。 C11对右值进行了严格的区分 C语言中的纯右值比如ab, 100 将亡值。比如表达式的中间结果、函数按照值的方式进行返回。 三、引用与右值引用比较
在C98中的普通引用与const引用在引用实体上的区别
int main()
{// 普通类型引用只能引用左值不能引用右值int a 10;int ra1 a; // ra为a的别名//int ra2 10; // 编译失败因为10是右值const int ra3 10;const int ra4 a;return 0;
}
注意 普通引用只能引用左值不能引用右值const引用既可引用左值也可引用右值。 C11中右值引用只能引用右值一般情况不能直接引用左值。
int main()
{// 10纯右值本来只是一个符号没有具体的空间// 右值引用变量r1在定义过程中编译器产生了一个临时变量r1实际引用的是临时变量int r1 10;r1 100;int a 10;int r2 a; // 编译失败右值引用不能引用左值return 0;
}
四、值的形式返回对象的缺陷 如果一个类中涉及到资源管理用户必须显式提供拷贝构造、赋值运算符重载以及析构函数否则编译器将会自动生成一个默认的如果遇到拷贝对象或者对象之间相互赋值就会出错比如
class String
{
public:String(char* str ){if (nullptr str)str ;_str new char[strlen(str) 1];strcpy(_str, str);}String(const String s): _str(new char[strlen(s._str) 1]){strcpy(_str, s._str);}String operator(const String s){if (this ! s){char* pTemp new char[strlen(s._str) 1];strcpy(pTemp, s._str);delete[] _str;_str pTemp;}return *this;}String operator(const String s){char* pTemp new char[strlen(_str) strlen(s._str) 1];strcpy(pTemp, _str);strcpy(pTemp strlen(_str), s._str);String strRet(pTemp);return strRet;}~String(){if (_str) delete[] _str;}
private:char* _str;
};
int main()
{String s1(hello);String s2(world);String s3(s1 s2);return 0;
}
上述代码看起来没有什么问题但是有一个不太尽人意的地方 在operator中strRet在按照值返回时必须创建一个临时对象临时对象创建好之后strRet就被销毁了最后使用返回的临时对象构造s3s3构造好之后临时对象就被销毁了。仔细观察会发现strRet、临时对象、s3每个对象创建后都有自己独立的空间而空间中存放内容也都相同相当于创建了三个内容完 全相同的对象对于空间是一种浪费程序的效率也会降低而且临时对象确实作用不是很大那能否对该种情况进行优化呢 五、移动语义
C11提出了移动语义概念即将一个对象中资源移动到另一个对象中的方式可以有效缓解该问题。
在C11中如果需要实现移动语义必须使用右值引用。上述String类增加移动构造
String(String s): _str(s._str)
{s._str nullptr;
}
因为strRet对象的生命周期在创建好临时对象后就结束了即将亡值C11认为其为右值在用strRet构造临时对象时就会采用移动构造即将strRet中资源转移到临时对象中。而临时对象也是右值因此在用临时对象构造s3时也采用移动构造将临时对象中资源转移到s3中整个过程只需要创建一块堆内存即可既省了空间又大大提高程序运行的效率。
注意 1. 移动构造函数的参数千万不能设置成const类型的右值引用因为资源无法转移而导致移动语义失效。 2. 在C11中编译器会为类默认生成一个移动构造该移动构造为浅拷贝因此当类中涉及到资源管理时用户必须显式定义自己的移动构造。 六、右值引用引用左值 按照语法右值引用只能引用右值但右值引用一定不能引用左值吗因为有些场景下可能真的需要用右值去引用左值实现移动语义。当需要用右值引用引用一个左值时可以通过move函数将左值转化为右值。C11中std::move()函数位于 头文件中该函数名字具有迷惑性它并不搬移任何东西唯一的功能就是将一个左值强制转化为右值引用然后实现移动语义。 templateclass _Ty
inline typename remove_reference_Ty::type move(_Ty _Arg) _NOEXCEPT
{
// forward _Arg as movable
return ((typename remove_reference_Ty::type)_Arg);
}
注意 1. 被转化的左值其生命周期并没有随着左值的转化而改变即std::move转化的左值变量lvalue不会被销毁。 2. STL中也有另一个move函数就是将一个范围中的元素搬移到另一个位置。 int main()
{
String s1(hello world);
String s2(move(s1));
String s3(s2);
return 0;
}
注意以上代码是move函数的经典的误用因为move将s1转化为右值后在实现s2的拷贝时就会使用移动构造此时s1的资源就被转移到s2中s1就成为了无效的字符串。 使用move的一个例子
class Person
{
public:Person(char* name, char* sex, int age): _name(name), _sex(sex), _age(age){}Person(const Person p): _name(p._name), _sex(p._sex), _age(p._age){}
#if 0Person(Person p): _name(p._name), _sex(p._sex), _age(p._age){}
#elsePerson(Person p): _name(move(p._name)), _sex(move(p._sex)), _age(p._age){}
#endif
private:String _name;String _sex;int _age;
};
Person GetTempPerson()
{Person p(prety, male, 18);return p;
}
int main()
{Person p(GetTempPerson());return 0;
}七、完美转发
完美转发是指在函数模板中完全依照模板的参数的类型将参数传递给函数模板中调用的另外一个函数。
void Func(int x)
{// ......
}
templatetypename T
void PerfectForward(T t)
{Fun(t);
}
PerfectForward为转发的模板函数Func为实际目标函数但是上述转发还不算完美完美转发是目标函数总希望将参数按照传递给转发函数的实际类型转给目标函数而不产生额外的开销就好像转发者不存在一样。
所谓完美函数模板在向其他函数传递自身形参时如果相应实参是左值它就应该被转发为左值如果相应实参是右值它就应该被转发为右值。这样做是为了保留在其他函数针对转发而来的参数的左右值属性进行不同处理比如参数为左值时实施拷贝语义参数为右值时实施移动语义。
C11通过forward函数来实现完美转发, 比如
void Fun(int x)
{ cout lvalue ref endl;
}
void Fun(int x)
{cout rvalue ref endl;
}
void Fun(const int x)
{ cout const lvalue ref endl;
}
void Fun(const int x)
{ cout const rvalue ref endl;
}templatetypename T
void PerfectForward(T t) { Fun(std::forwardT(t)); }
int main()
{PerfectForward(10); // rvalue refint a;PerfectForward(a); // lvalue refPerfectForward(std::move(a)); // rvalue refconst int b 8;PerfectForward(b); // const lvalue refPerfectForward(std::move(b)); // const rvalue refreturn 0;
}
八、右值引用作用 C98中引用作用因为引用是一个别名需要用指针操作的地方可以使用指针来代替可以提高代码的可读性以及安全性。
C11中右值引用主要有以下作用 1. 实现移动语义(移动构造与移动赋值) 2. 给中间临时变量取别名 int main()
{
string s1(hello);
string s2( world);
string s3 s1 s2; // s3是用s1和s2拼接完成之后的结果拷贝构造的新对象
stirng s4 s1 s2; // s4就是s1和s2拼接完成之后结果的别名
return 0;
} 3. 实现完美转发