引用主要就是为了减少一些不必要的拷贝。传的啥就用它的那一份空间
左值引用
左值引用:也就是传统的引用,大家都比较熟悉,就是为了给我们的变量起个别名,和取地址然后得到指针其实差不多
左值引用有个问题,常值/临时变量是不能传给左值引用的,也有办法可以,就是用常量左值引用(前面加个const)
int &x = 3; // 错误,常值
auto& a = A(); // 错误,临时值
const auto& a = A();
const int &x = 3;
常左值引用也有个问题,我不能修改这个变量,要是我就想要修改呢,而且我也不想另外复制一份对象。(问:为什么不直接设计为常值/临时变量可以给左值引用赋值,然后可以修改?)
右值引用
这时就有了右值引用,它只用一份对象的空间,然后跟一般变量一样可以修改。
(当然你也可以在前面加const,成为常量右值引用 ,没啥意义?不如直接用常量左值引用?)
右值的关键在于这个值它在赋值完之后我们就不会再使用它了(或者说我们不再依赖它原来的值了,见下面的move语义)。
move 转换为右值
所以我们有了move,它可以把不管左值右值(因为模板参数是T&&类型,涉及引用折叠)都转换为右值,可以看到它的实现特别简单,就是用static_cast进行了一个强转
/**
* @brief Convert a value to an rvalue.
* @param __t A thing of arbitrary type.
* @return The parameter cast to an rvalue-reference to allow moving it.
*/
template<typename _Tp>
constexpr typename std::remove_reference<_Tp>::type&&
move(_Tp&& __t) noexcept
{ return static_cast<typename std::remove_reference<_Tp>::type&&>(__t); }
这样通过move转换一个值为右值之后我们就可以把它这个值内的东西移走了,也就对应了我们为类实现的移动构造/移动拷贝函数了
比如下面这个例子,这里我们就复用了b1的ptr指针,如果不这样的话,我们可能就得在拷贝构造函数的时候深拷贝一下了,再动态分配一个int。如果这个指针指向的对象特别大那,那移动构造函数的优势就会特别明显了。
class B {
private:
int *ptr;
public:
B(int x): ptr(new int(x)) {}
B(B &&b): ptr(b.ptr) {
b.ptr = nullptr;
}
~B() {
delete ptr;
}
int* get() {
return ptr;
}
};
int main(int argc, char const *argv[])
{
B b1(3);
B b2(move(b1));
cout << b1.get() << endl; // 0
cout << b2.get() << endl; // 0x602000000010
return 0;
}
move完一个变量之后,这个东西里面的值因为被(移动构造/拷贝构造函数)移走了(比如把指针赋值为nullptr),我们不应该再依赖原来的变量的值,所以比如像这里的移动构造函数,我们把这个右值对象里的ptr设为0。
网友评论