右值引用
左值引用
引用分为左值引用和右值引用。我们平常讲的引用指的都是左值引用。
int num = 6;
int& refer = num; // 这是左值引用
// int& refer = 6; // 错误,不能将右值赋值到左值引用上通过左值引用不能传入右值的这个特性,我们可以实现一个只能传入左值的函数:
void func(int& num)
{
++num;
}
int main()
{
func(3); // 错误,右值不能作为参数传入函数
}右值引用
右值引用和左值引用正相反,右值引用只允许右值赋值,用&&来声明右值引用:
int&& refer1 = 3;
int num = 3;
// int&& refer2 = num; // 错误,左值不能赋值到右值引用通过这个特性,我们就能创建一个只能传入右值的函数:
int func(int&& num)
{
return ++num;
}
int main()
{
int num = 4;
// func(num); // 错误,不能传入左值
cout << func(4) << endl; // 5
}你可以尝试判断一下,这个有没有报错?
int func(int&& num)
{
return ++num;
}
int func(int& num)
{
return num + 5;
}
int main()
{
int num = 4;
cout << func(func(num)) << endl;
}上面的函数不仅不会报错,还会输出10。解释如下:先调用func(num),匹配到左值的函数,返回值为9。然而返回值属于右值,这就会匹配到传入右值的函数,返回一个10。
右值引用是左值
右值引用传入右值,但本身是个左值,也就是说,这样的代码是不会报错的:
int&& refer1 = 8;
int& refer2 = refer1;
cout << refer2 << endl; // 8左值变右值
通过std::move可以实现左值转换到右值。再平常使用中,如果将一个左值转换到了右值,应当保证以后再也不使用这个左值了,虽然没有硬性规定。
int&& refer1 = 8;
int& refer2 = refer1;
int&& refer3 = std::move(refer2); // 之后使用遗忘掉refer2的存在吧
cout << refer3 << endl; // 8使用
move的代码应该显式标注使用了std::move,避免潜在的命名冲突。
移动构造函数和移动赋值运算符
通过右值引用的特性,我们可以创建传入右值的构造函数。这称作为移动构造函数。下面是一个示例:
class Example
{
public:
Example() = default; // 默认构造函数
Example(int v) : data(v) {} // 构造函数
Example(const Example& lval) : data(lval.data) {} // 拷贝构造函数
Example(Example&& rval) : data(rval.data) // 移动构造函数
{
rval.data = 0;
}
private:
int data;
};移动构造函数可以避免额外的拷贝开销。下面用一个示例来说明:
auto p1 = new Example(3);
auto p2 = new Example(*p1);
auto p3 = new Example(std::move(*p2));
// 移动对象需要两步:右值构造调用一次构造函数,移动调用一次移动构造函数
// 移动构造函数只是转移了所有权,拷贝需要额外开辟内存,复制。还可以创建移动赋值运算符,和重载赋值运算符类似:
class Example
{
public:
Example() = default; // 默认构造函数
Example(int v) : data(v) {} // 构造函数
Example(const Example& lval) : data(lval.data) {} // 拷贝构造函数
Example(Example&& rval) : data(rval.data) // 移动构造函数
{
rval.data = 0;
}
Example& operator=(Example&& rval)
{
data = 0; // 先清空自己占有的内存,相当于析构函数
data = rval.data; // 然后执行移动操作
return *this;
}
private:
int data;
};
评论区