数据在计算机里按址存取,我们只有知道一个数据的准确地址才能访问到实际数据。
左值:根据字面意思,可以出现在赋值运算符=左边的叫做左值,严格来讲左值指的是有固定地址的值,其值可确定某个对象或函数的标识。
右值:和左值相对,右值指的是出现在=右侧的值,而现在右值指的是不能用取址符取址的值。
int a = 10;
int b = a + 10;
按照我们上面说的,出现在=左边的值为左值,第一行中a为左值,第二行a出现在=右侧,那么a便是右值吗?
任意一个值要么是左值要么是右值,左值可以拥有左值属性和右值属性,换言之,但凡能使用右值的地方都能使用左值,反之则不行。
&a; //正确,a是一个左值,有固定地址。
&10; //错误,10不是左值,所以没法取址。
引用:引入了对象的一个同义词。定义引用的表示方法与定义指针相似,只是用&代替了*。
左值引用:左值引用就是传统意义上的引用,分为普通引用和const引用。
void fun1(string s)
{
cout << s << endl;
}
void fun2(string const& s)
{
cout << s << endl;
}
int main()
{
string str = "hello world";
fun1(str);
fun2(str);
return 0;
}
fun2相对于fun1少了一步拷贝。
右值引用:右值引用使用&&声明,右值引用也是引用,所以右值虽然绑定在了右值但它仍然是左值。
例1:
int main()
{
int&& a = 11;
a = 12;
cout << a;
return 0;
}
可以对右值引用的值进行修改。
思考:为什么能修改呢?
右值引用也是引用!!!
所以右值虽然绑定在了右值但它仍然是左值!!!
例2:
void test(int&& i)
{
cout << i << endl;
}
int main()
{
int a = 10;
int&& b = 10;
test(a); //错误,无法将右值引用绑定到左值
test(b); //错误,无法将右值引用绑定到左值???
test(10); //正确
return 0;
}
形参永远是左值,虽然b的类型是右值引用,但是b本身是左值,b出现在了=左侧且可以使用取址符&取址。
例3:
struct testMove
{
testMove()
{
m_value = nullptr;
size = 0;
cout << "testMove" << endl;
}
void resize(size_t s)
{
m_value = new int[10000];
size = 10000;
}
~testMove()
{
delete[]m_value;
cout << "~testMove" << endl;
}
testMove(const testMove& other)
{
m_value = new int[other.size];
memcpy(m_value, other.m_value, other.size);
cout << "copy" << endl;
}
int *m_value;
int size;
};
testMove makeTest()
{
testMove st;
st.resize(10000);
return st;
}
int main()
{
testMove st = makeTest();
return 0;
}
有了右值引用之后以后我们对testMove进行扩展。
testMove(testMove&& other)
{
m_value = other.m_value;
size = other.size;
other.m_value = nullptr;
other.size = 0;
cout << "move" << endl;
}
通过右值引用可以实现移动构造。
万能引用:
template
void test(T &&t)
{
cout << t<
T实际类型 |
最终类型 |
T |
R&& |
T& |
R& |
T&& |
R&& |
std::move:移动函数
template
_NODISCARD constexpr remove_reference_t<_Ty>&& move(_Ty&& _Arg) noexcept { // forward _Arg as movable
return static_cast&&>(_Arg);
}
……
#if _HAS_NODISCARD
#define _NODISCARD [[nodiscard]] //属性,该属性表示返回值不应该被舍弃,当被舍弃时编译器会进行警告。
……
template
using remove_reference_t = typename remove_reference<_Ty>::type;
……
template
struct remove_reference {
using type = _Ty;
using _Const_thru_ref_type = const _Ty;
};
完美转发std::forward
考虑一个问题:对于同一个功能,有时候我们需要对左值和右值调用不用的方法。
例:
void test(int&& i)
{
cout << "&&" << endl;
}
void test(int& i)
{
cout << "&" << endl;
}
template
void run_test(T &&t)
{
test(std::forward(t);
}
int main()
{
int a = 10;
run_test(a);
run_test(10);
return 1;
}
//针对左值
template
_NODISCARD constexpr _Ty&& forward(
remove_reference_t<_Ty>& _Arg) noexcept { // forward an lvalue as either an lvalue or an rvalue
return static_cast<_Ty&&>(_Arg);
}
//针对右值
template
_NODISCARD constexpr _Ty&& forward(remove_reference_t<_Ty>&& _Arg) noexcept { // forward an rvalue as an rvalue
static_assert(!is_lvalue_reference_v<_Ty>, "bad forward call");
return static_cast<_Ty&&>(_Arg);
}