左值与右值
左值:
即等号左边的值。我们通常使用等号来赋值的时候,左边的是被赋值的对象。
那么显然,左值就是一个有名字、有内存的值。
比如:
int main()
{
int a=1;
int b=2;
//这里的a、b均为左值
return 0;
}
右值:
即等号右边的值。同样的,我们使用赋值的时候,等号右边的值通常是一个已知的、确定的值。
也就是说,右值是一个没有名字、没有内存的值,通常可以用来指一些临时量。
比如:
class MyString
{
public:
MyString():p(nullptr){};
~MyString()
{
delete p;
p=nullptr;
}
private:
char *p;
};
MyString fun(char* p)
{
return MyString(p);//这里返回的值就是一个临时量 它是右值
}
int main()
{
int k=12;//这里的12也是右值
return 0;
}
move与forward
move:
move ,底层实现实际上就是一个类型的强转,它会把一个左值强转成右值。
在vector的push_back函数的实现中,我们需要针对左值和右值进行不同的存储操作。
然而,由于我们存储右值使用的符号本身是一个左值(比如上面的例子中,12是右值,但我们使用k来存储它的时候,k是一个左值),所以无法直接调用右值参数对应的函数,此时就需要使用move强转成右值。
void construct(T *p, const T& val)//对象的构造
{
new(p) T(val);//定位new
}
void construct(T *p, T&& val)
{
new(p) T(std::move(val));//为了能够匹配到右值引用参数对应的拷贝构造函数
}
void push_back(const T& val)//左值
{
if(full())
expand();
construct(_last,val);
_last++;
}
void push_back(T&& val)//右值
{
if(full())
expand();
construct(_last,std::move(val));//为了匹配到右值参数对应的construct函数
_last++;
}
forward:
上面的代码,我们可以看到为了实现面对两种参数的一种功能,我们使用了重复率很高的两段代码,在C++中,这种情况我们完全可以用函数模板进行优化。
forward,可以自动识别参数是左值还是右值。(释义为:类型的完美转发)
所以我们的代码可以优化为:
template<typename Ty>
void construct(T *p, Ty&& val)
{
new(p) T(std::forward<Ty>(val));
}
template<typename Tk>
void push_back(Tk&& val)//传值时触发引用折叠
//& &&==>& && &&==>&&
{
if(full())
expand();
construct(_last,std::forward<Tk>(val));//自动识别val的类型
_last++;
}```