C++11右值
- 类型与值类别
- 左值
- 纯右值
- 将亡值
- 内置类型
- 类类型
- 左值引用和右值引用
- 内置类型
- 右值引用:
- 具有右值引用自身为左值。
- 右值引用与函数重载
- 函数返回值
- 自定义类型
- 右值引用
- 构造函数的隐式转换
- 右值引用与函数重载
- 类类型作为函数的返回值
- 右值引用与函数模板
- 泛化版本,刻画版本
- 模板
- 引用型别
- 右值引用变成左值引用
- 完美转发和forward
- 引用叠加
类型与值类别
C++表达式可以按照两个独立的属性分为:类型和值类别。而值类别又分为三种:左值,右值,将亡值,每个之值类别都有与之某种引用类型对应。
其中将亡值具名就是左值,不具名就是右值,所以呢左值和将亡值合成泛左值,将亡值和纯右值称为右值。
左值
能够取地址的表达式称为左值表达式。
int main() {
int a=10;
const int b=20;
int* ip=&a;
const int* cp=&b;
return 0;
}
纯右值
纯右值本身就是字面值,例如:43,false,34.24;
具名变量或对象都是左值,而右值不具名。
int main() {
int a=10;//可以取地址,左值
const int b=20;//常性整型,可以取地址,左值
double dx=12.34;//double类型的值,可以取地址,左值
int* p=nullptr;//左值
//字面常量12,20,12.34,nullptr这些不可以取地址,称为右值,不可以改变也成为纯右值
return 0;
}
将亡值
内置类型
在表达式运行或计算过程中产生的临时变量或临时对象称之为将亡值,临时值可能是字面值,也可能是一个不具名的对象。
int func() {
int x=10;
return x;
}
int main() {
int a=10,b=20;
int i-0;
i=a+b;
++i;
i++;
&a;
a=func();
return 0;
}
算数表达式,逻辑表达式,比较表达式,取地址表达式等,计算结果相当于字面值,(数据实际存储在CPU的数据寄存器中),所以是将亡值,不可改,所以是纯右值。
对i加1赋值给i,返回值就是i,i具名就是左值。++i的结果也是具名的,所以是左值。
对i++而言,首先对i进行一次拷贝,将得到的副本作为返回结果,再对结果进行+1,由于i++的结果是对i+1前靠别了一份,不具名,所以是将亡值,此将亡值不可写,也是字面量,所以是纯右值。
类类型
自定义类型:
class Int {
int _value;
public:
Int(int x = 0) :_value(x) { cout << "Create Int:" << _value << endl; }
Int(const Int& it) :_value(it._value) { cout << "Copy Create Int:" << this << endl; }
Int& operator=(const Int& it) {
if (this != &it) {
_value = it._value;
cout << this << "=" << &it << endl;
}
return *this;
}
~Int() { cout << "Destory Int:" << _value << endl; }
void Setvalue(int x) { _value = x; }
int Getvalue()const { return _value; }
void Showvalue()const { cout << _value << endl; }
};
以上面自定义Int类型为例执行下面主程序:
int main() {
Int a(1);//左值
const Int b(20);//常性左值
a.Showvalue();
Int(2).Showvalue();//不具名对象,右值
Int(4).Setvalue(20);//右值也可以调用成员函数,可以改变其属性,纯右值不可以改变
Int* ip = &Int(12);//error 右值不能取地址
return 0;
}
函数返回类型是类类型对象:
Int func(int x) {
Int tmp(x);
return tmp;
}
int main() {
Int a = 1;//构造函数创建对象,左值
a = func(2);//函数内构造出tmp对象,然后通过拷贝构造产生将亡值,然后释放tmp对象
//接着将该将亡值都西昂通过运算符赋值赋值给a对象,然后析构将亡值对象。
Int b=func(3);//直接进行移动拷贝构造。
Int(3).Setvalue(20);//创建右值调用设置属性。该语句结束析构该右值对象
func(4).Showvalue();//函数内构造出tmp对象,然后通过拷贝构造产生将亡值,然后释放tmp对象
//通过该将亡值调用Show函数,最后释放将亡值对象
func(5).Setvalue(100);//同上,仅仅是改变数据
&func(7);//error 将亡值不具名是右值,不能取地址。
return 0;
}
对象生存期:
Int fun(int x) {
Int tmp(x);
return tmp;
}
int main() {
Int(1).Showvalue();
func(2).Showvalue();//将亡值调用
return 0;
}
左值引用和右值引用
内置类型
右值引用:
int main() {
int x = 10;
int& a = x;//左值引用
const int& b = x;//常左值引用,全能引用
int** rc = x;//error 右值引用不能引用左值
int&& rd = 20;//右值引用引用纯右值
const int& e = 10;//常左值引用,万能引用
return 0;
}
具有右值引用自身为左值。
int main() {
int &&a=10;
int&& b=a;//error 右值引用具名就变成了左值
int& c=a;
const int& cd=a;//万能引用
return 0;
}
右值引用与函数重载
以前的博客写了很多次了,这里展示一下代码:
void func(int& val) {//A
cout << "L value reference" << endl;
}
void func(const int& val) {//B
cout << "const L value reference" << endl;
}
void func(int&& val) {//C
cout << "R value reference" << endl;
}
int main() {
int a = 10;
const int b = 20;
func(a);//1.A 2.B
func(b);//B
func(10);//1.C 2.B
return 0;
}
函数返回值
返回值为值对象,返回将亡值对象。如果是返回左值引用,不能赋值给右值,如果是右值引用不可以赋值给左值。但是都可以赋值给左值常引用。虽然可以编译通过,但是呢我觉得如果返回值的生存周期受函数影响,迟早在运行过程中会出错。
int funa() {//A
int x = 10;
return x;
}
int& funb() {//A
int x = 10;
return x;
}
int&& func() {//A
int x = 10;
return int(10);
}
int main() {
int a = funa();
int& b = funa();//error funa返回右值
const int& c = funa();
int&& d = funa();
int e = funb();
int& f = funb();
const int& g = funb();
int&& h = funb();//左值无法赋值给右值
int i = func();
int& j = func();//无法将右值给左值
const int& k = func();
int&& l = func();
return 0;
}
自定义类型
右值引用
还是上面的Int类为例:
int main() {
Int a = Int(1);
Int& b = Int(2);//error 不具名对象为右值,
const Int& c = Int(3);
Int&& d = Int(4);
a.Showvalue();
//b.Showvalue();
c.Showvalue();
d.Showvalue();
return 0;
}
无论声明左值引用还是右值引用都必须立即进行初始化,因为引用类型本身并不拥有所绑定对象的内存,只是该对象的一个别名,通过右值引用的声明,该右值又重获新生,其生命周期与右值引用类型名的生命周期一样,只要改右值引用名话或者,改右值临时两就一直存活下去。
构造函数的隐式转换
将内置类型隐式转换为Int类型,初始值为1,这就是构造函数的一个作用
int main() {
const Int& a=1;
Int &&b=2;
Int &&c=b;//error b变成了左值
Int&dr=b;
return 0;
}
此处用了转化多了一步,从1转换为Int类型,在转换为常性,
int main() {
const Int& a = 1;
//const int& a=(Int)(1);
Int&& b = 2;
//Int&& b=(Int)(2);
Int&& c = b;//error具名变成左值
Int& dr = b;
return 0;
}
在构造函数中加入explicit明确关键字,组织构造函数的隐式转换:
explicit Int(int x = 0) :_value(x) {
cout << "Create Int:" << _value << endl;
}
右值引用与函数重载
void func(Int& val) {//A
cout << "L value reference" << endl;
}
void func(const Int& val) {//B
cout << "const L value reference" << endl;
}
void func(Int&& val) {//C
cout << "R value reference" << endl;
}
int main() {
Int a(1);
const Int b(2);
func(a);
func(b);
func(Int(3));
}
类类型作为函数的返回值
左值常引用是万能引用,可以接收左值,右值,常性左值和常性右值,普通左值不能接收右值。
Int func(int x) {
Int tmp(x);
return tmp;
}
int main() {
Int a = func(1);
Int x(0);
x = func(2);
Int& b = func(3);//右值给左值
const Int& c = func(4);
Int&& d = func(5);
}
右值引用与函数模板
泛化版本,刻画版本
template<class T>//A
class Test {//Test<int> Test<int*>...一切类型都可以接收
};
template<class T>//B
class Test<T*> {//Test<int*>...指针类型
};
template<>//C
class Test<const char*>{//Test<const char*>
};
A:泛化版本 B:部分刻画版本 C:刻画版本
模板
template<class _Ty>
struct my_remove_reference {
using type = _Ty;
my_remove_reference() {
type x;
cout << "_Ty" << endl;
}
};
template<class _Ty>
struct my_remove_reference <_Ty&> {
using type = _Ty;
my_remove_reference() {
type x;
cout << "_Ty&" << endl;
}
};
template<class _Ty>
struct my_remove_reference {
using type = _Ty;
my_remove_reference()<_Ty &&> {
type x;
cout << "_Ty&&" << endl;
}
};
int main() {
my_remove_reference<int>();//A
my_remove_reference<int&>();//B
my_remove_reference<int&&>();//C
my_remove_reference<const int&>();//D
return 0;
}
上面代码主函数中,调用的参数不同使用不同的模板,但是其中_Ty类型是什么类型呢?
A:int B:int C:int D:const int
template<class _Ty>
using my_remove_reference_t =
typename my_remove_reference<_Ty>::type;
int main() {
my_remove_reference_t<int>a;
my_remove_reference_t<int&>b;
my_remove_reference_t<int&&>c;
my_remove_reference_t<const int&>d;
return 0;
}
思考上面代码运行结果。
引用型别
代码应用如下:
template<class T>
void func(T&& x) {//未定义的引用类型,被什么值类型初始化就是什么值
int e = 10;
T y = e;
}
int main() {
int a = 10;
const int b = 20;
int&& c = 30;
int& v = a;
func(a);//左值
func(v);//左值引用
func(b);//常性左值
func(30);//右值
return 0;
}
右值引用变成左值引用
void Print(int& val) {//A
cout << "L value reference" << endl;
}
void Print(const int& val) {//B
cout << "const L value reference" << endl;
}
void Print(int&& val) {//C
cout << "R value reference" << endl;
}
template<class T>
void TestRvalue(T&& x) {
Print(x);
Print(std::forward<T>(x));
}
int main() {
int a = 1;
const int& b = 2;
int&& c = 3;
TestRvalue(a);
TestRvalue(b);
TestRvalue(c);
TestRvalue(10);
return 0;
}
运行上面代码我们会发现因为具名所以导致了输出结果全为左值或者常性左值。
完美转发和forward
我们在写代码过程中会发现和上面情况一样,右值引用变成左值引用:右值没有办法让他不具名,但是他具名之后又会转成左值,所以出现了完美转发:
void Print(int& val) {//A
cout << "L value reference" << endl;
}
void Print(const int& val) {//B
cout << "const L value reference" << endl;
}
void Print(int&& val) {//C
cout << "R value reference" << endl;
}
template<class T>
void TestRvalue(T&& x) {
Print(std::forward<T>(x));
}
int main() {
int a = 1;
const int& b = 2;
int&& c = 3;
TestRvalue(a);
TestRvalue(b);
TestRvalue(c);
TestRvalue(10);
return 0;
}
运行结果:
引用叠加
右值引用与右值引用叠加是右值引用
右值引用与左值引用叠加是左值引用
右值引用与左值常引用叠加式是左值常引用。