3.1二者的对比之内置类型
内置类型的无名对象(右值)为纯右值,其值本身不可改变
int main()
{
int a=10;
const int b=20;
int& ra=a;//ok,左值引用
const int& rb=b;//ok,常性左值引用
const int& crv=30;//ok,也叫万能引用,即可以引用左值也可以引用右值
/*上句实际上是变为:
int tmp=30;
const int& crv=tmp;*/
int&& rv=30;//ok,右值引用
/*上句实际上是变为:
int tmp=30;
int& rv=tmp;*/
//常性左值引用和右值引用的区别:
crv+=100;//error
rv+=100;//ok,不是将30改为100,30为纯右值,不能改变。实际是将tmp的值改为100
return 0;
}
3.2二者的对比之自己设计的类型
自己设计的类型的无名对象(右值)为将亡值,其值可以改变
class Int
{
int value;
public:
Int(int x = 0) :value(x) { cout << "create " << this << endl; }
~Int() { cout << "destroy " << this << endl; }
Int(const Int& it) :value(it.value)
{
cout << &it << " copy " << this << endl;
}
Int& operator=(const Int& it)
{
if (this != &it)
{
value = it.value;
}
cout << &it << " operator= " << this << endl;
return *this;
}
void PrintValue()const
{
cout<<"value: "<<value<<endl;
}
Int& SetValue(int x)
{
value=x;
return *this;
}
};
Int fun(int x)
{
Int tmp(x);
return tmp;
}
int main()
{
/*
Int a(10);//左值
const Int b(20);//常左值
Int& ra=a;//左值引用
const Int& rb=b;//常性左值引用
*/
Int&& rv=Int(30);//右值引用,引用无名对象
Int&& rf=fun(40);//引用函数返回值的将亡值对象
rv.PrintValue();//ok
rv.SetValue(300);//ok
rv.PrintValue();
rf.PrintValue();//ok
rf.SetValue(400);//ok
rf.PrintValue();
return 0;
}
结果:
3.3将亡值的生存期问题
3.3.1使用无名对象作为例子:
int main()
{
Int(10).PrintValue();
cout<<"main end"<<endl;
return 0;
}
结果:
将亡值在该行语句执行完后就会消亡,而不是等到主函数结束才消亡,其生存期就只在其调用点处。
当使用右值引用引用该无名对象(将亡值)时:
int main()
{
Int&& rv=Int(30);
rv.PrintValue();
cout<<"main end"<<endl;
return 0;
}
结果:
由此来看,使用右值引用去引用将亡值,该将亡值的生存期就会和右值引用的标识符相同。
3.3.2以函数返回值作为例子:
int main()
{
fun(10).PrintValue();
cout<<"main end"<<endl;
return 0;
}
结果:
当使用右值引用引用该无名对象(将亡值)时:
int main()
{
Int&& rf=fun(10);
rf.PrintValue();
rf.SetValue(100);
rf.PrintValue();
cout<<"main end"<<endl;
return 0;
}
结果:
与使用无名对象作为例子时的结果相同。
总结:将亡值在该行语句执行完后就会消亡,而不是等到主函数结束才消亡,其生存期就只在其调用点处。但是使用右值引用去引用将亡值,该将亡值的生存期就会和右值引用的标识符相同。
3.3.3将右值引用设置为静态,其生存期如何
int func()
{
static Int&& rf=fun(10);//.data区
rf.PrintValue();
rf.SetValue(100);
rf.PrintValue();
cout<<"func end"<<endl;
return 0;
}
int main()
{
cout<<"main begin"<<endl;
func();
cout<<"main end"<<endl;
return 0;
}
结果:
右值引用引用将亡值后,将亡值的生存期就随从右值引用的标识符的生存期,而右值引用rf为静态右值引用,存储在.data区,.data区的数据是在程序运行结束才进行释放,所以当整个程序结束才会销毁该将亡值。
如果rf为非静态右值引用时,将亡值的生存期就只在func函数中有效,即结果为:
3.4具名右值->泛左值
int main()
{
Int&& rv=Int(30);
Int&& rf=fun(40);
int&& a=10;
a+=1;//ok
&rv;//ok
&rf;//ok
&a;//ok
Int&& ra=rv;//error
Int&& rb=rf;//error
int&& b=a;//error
//此时,rv、rf和a都可以取地址,被称为泛左值,所以不可以再用右值引用去引用rv/rf和a
Int& rc=rv;//ok
Int& rd=rf;//ok
int& c=a;//ok
const Int& re=rv;//ok
const Int& rg=rf;//ok
const int& d=a;
return 0;
}
3.5值类别转换
int main()
{
Int a(10);//左值
const Int b(20);//常左值
Int&& rv=Int(30);//右值
//如何让右值引用引用左值:
Int&& ra=a;//error
Int&& rb=static_cast<Int&&>(a);//ok,C++的静态类型转换
Int&& rc=const_cast<Int&&>(b);//ok,C++的去常性类型转换
//以上两句均可以使用C语言的强转来进行,但C语言的强转是不安全的,尽量使用C++的四种类型转换。
return 0;
}
3.6函数返回类型不同
3.6.1以值的形式返回(会构建将亡值)
Int fun(int x)
{
Int tmp(x);
return tmp;
}
const Int func(int x)
{
Int tmp(x);
return tmp;
}
int main()
{
Int a=fun(1);//ok,编译器进行优化,直接构建a对象
Int& b=fun(2);//error,fun(2)是一个右值不能以左值引用引用
const Int& c=fun(3);//ok,左值常引用,可以引用右值
Int&& d=fun(4);//ok,右值引用引用右值
const Int&& e=fun(5);//ok,右值常引用
Int a=func(1);//ok,编译器进行优化,直接构建a对象,且该对象不具有常性
a.SetValue(100);//ok
a.PrintValue();//ok
Int& b=func(2);//error,fun(2)是一个右值不能以左值引用引用
const Int& c=func(3);//ok,左值常引用,可以引用常性右值
Int&& d=func(4);//error,右值引用不能引用常性右值
const Int&& e=func(5);//ok,右值常引用
//对于常性右值引用,编译器编译时将其看成常性左值引用
return 0;
}
3.6.2以左值引用的形式返回(及其不安全)
Int& fun(int x)
{
Int tmp(x);
return tmp;
}
int main()
{
Int a=fun(1);//编译正确
Int& b=fun(2);//编译正确
const Int& c=fun(3);//编译正确
Int&& d=fun(4);//error,不能使用左值引用初始化右值引用
cout<<"main end"<<endl;
return 0;
}
不安全的原因:不能将函数的局部对象或者局部变量以引用的形式返回。引用本质上是指针,在以引用返回时不会构建将亡值,而是直接将tmp对象的地址返回,返回结束后fun函数的栈空间被系统收回,tmp对象已经消亡,用已经消亡的对象去初始化a对象或者引用已死亡的对象是错误的,此时的引用b和c都是失效引用。
3.6.3以右值引用的形式返回(依然不安全)
Int&& fun(int x)
{
return Int(x);
}
int main()
{
Int a=fun(1);//ok
Int& b=fun(2);//error,左值引用不能被右值引用初始化
const Int& c=fun(3);//ok
Int&& rf=fun(4);//ok
a.PrintValue();//ok
//b.PrintValue();
c.PrintValue();//ok
rf.PrintValue();//ok
return 0;
}
a对象是以已经死亡的对象构建的,c、rf均引用已死亡的对象
3.6.4安全的以引用返回
当对象的生存期不受函数生存期的影响就可以以引用返回。例如:静态变量
Int& fun(int x)
{
static Int tmp(x);
return tmp;
}
void add()
{
int ar[10]={1,2,3,4,5,6,7,8,9,10};
}
int main()
{
Int a=fun(1);
Int& b=fun(2);
const Int& c=fun(3);
a.PrintValue();
b.PrintValue();
c.PrintValue();
add();
b.PrintValue();
c.PrintValue();
cout<<"main end"<<endl;
return 0;
}
全是1的原因是:静态对象只创建一次当程序结束才消亡。当第一次调用fun(1)函数时,静态对象tmp被创建并初始化为1,在返回时拷贝构造了a对象;当第二次调用fun(2)函数时,静态对象tmp在第一调用中已经创建好了不会再被创建,所以其值依然为1,并且b作为静态对象tmp的引用;同样的,c也作为其引用,所以值均为1。由于静态对象在.data区中,在调用add()函数后不会清扰tmp的数据,所以值依然为1。