左值与右值总结
左值与右值不是很好区分,一般认为:
普通类型的变量,因为有名字,可以取地址,都认为是左值
const修饰的常量,不可修改,只读类型的,理论上应该按照右值对待,但因为其可以取地址(如果只是const类型常量的定义,编译器不给其开辟空间,如果对该常量取地址时,编译器才为其开辟空间),C++11认为其是左值
如果表达式运行结果或单个变量是一个引用则认为是左值
如果表达式的运行结果是一个临时变量或者对象,认为是右值
C++11中的右值
右值引用,顾名思义就是对右值的引用。
C++11中,右值由两个概念组成:纯右值和将亡值。
纯右值
纯右值是C++98中右值的概念,用于识别临时变量和一些不跟对象关联的值。比如:常量、一些运算表达式(1+3)等
将亡值
声明周期将要结束的对象。比如:在值返回时的临时对象。
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()
{ if (_str) delete[] _str;}
private:
char* _str;
};
//假设现在有一个函数,返回值为一个String类型的对象:
String GetString(char* pStr) {
String strTemp(pStr);
return strTemp; }
int main()
{
String s1("hello");
String s2(GetString("world"));
return 0; }
GetString
函数返回的临时对象,将
s2
拷贝
构造成功之后,立马被销毁了
(
临时对象的空间被释放
)
,再没有其他作用;而
s2
在拷贝构造时,又需要分配
空间,一个刚释放一个又申请,有点多此一举
。

右值引用书写格式:
1.类型
&&
引用变量名字
=
实体;
右值引用最长常见的一个使用地方就是:与移动语义结合,减少无必要资源的开辟来提高代码的运行效率。
String(String&& s): _str(s._str)
{ s._str = nullptr; }
String&& GetString(char* pStr) {
String strTemp(pStr);
return strTemp; }
int main()
{
String s1("hello");
String s2(GetString("world"));
return 0; }
2.右值引用另一个比较常见的地方是:给一个匿名对象取别名,延长匿名对象的声明周期。
与引用一样,
右值引用在定义时必须初始化
。 通常情况下,右值引用不能引用左值
。
注意:
1.与引用一样,右值引用在定义时必须初始化。 通常情况下右值引用不能引用左值。
2.在C++11中,拷贝构造/移动构造/赋值/移动赋值函数必须同时提供,或者同时不提供,程序才能保证类同时具有拷贝和移动语义。
std::move()
它
并不搬移任何东西,唯一的功能
就是将一个左值强制转化为右值引用,通过右值引用使用该值,实现移动语义
。
注意:被转化的左值,其声 明周期并没有随着左右值的转化而改变,即std::move
转化的左值变量
lvalue
不会被销毁
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 0
Person(Person&& p)
: _name(p._name)
, _sex(p._sex)
, _age(p._age)
{}
#else
Person(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; }
move
更多的是用在声明周期即将结束的对象上。
为了保证移动语义的传递,程序员在编写移动构造函数时,最好使用
std::move
转移拥有资源的成员
为右值。
完美转发
完美转发是指在函数模板中,完全依照模板的参数的类型,将参数传递给函数模板中调用的另外一个函数
。
void Func(int x) {
// ......
}
template<typename T>
void PerfectForward(T t) {
Fun(t);
}
PerfectForward
为转发的模板函数,
Func
为实际目标函数。
完美转发是目标函 数总希望将参数按照传递给转发函数的实际类型转给目标函数,而不产生额外的开销。
所谓完美:
函数模板在向其他函数传递自身形参时,如果相应实参是左值,它就应该被转发为左值;如果相
应实参是右值,它就应该被转发为右值
。这样做是为了保留在其他函数针对转发而来的参数的左右值属性进 行不同处理(比如参数为左值时实施拷贝语义;参数为右值时实施移动语义)。