【C++基础】std::move用法介绍


前言

本文归纳总结了std::move的用法,使用场景,以及作用会对我们的程序带来哪些好处和使用时的注意事项。


一、std::move()使用场景

之前在右值引用和move语义总结中我们已经介绍过右值引用和移动语义,发现通过移动而不是拷贝会让我们的代码性能更高。但是移动有个要求是参数必须是右值,假设有如下代码:

#include <iostream>
#include <string>

template <typename T>
void mySwapCopy(T& a, T& b)
{
	T tmp { a }; // invokes copy constructor
	a = b; // invokes copy assignment
	b = tmp; // invokes copy assignment
}

int main()
{
	std::string x{ "abc" };
	std::string y{ "de" };

	std::cout << "x: " << x << '\n';
	std::cout << "y: " << y << '\n';

	mySwapCopy(x, y);

	std::cout << "x: " << x << '\n';
	std::cout << "y: " << y << '\n';

	return 0;
}

此函数传入两个 T 类型的对象(在本例中为 std::string),通过创建三次拷贝来交换它们的值。因此,该程序打印:

x: abc
y: de
x: de
y: abc

通过右值引用和move语义总结中,我们知道拷贝的性能差于移动,而且在这个例子中拷贝也是非必要的,因为我们只想交换a, b的值,完全没有必要拷贝一份,直接资源转移就可以。
所以通过移动可以让我们的代码更高效,但是问题是参数 a 和 b 是左值引用,而不是右值引用,因此我们没有办法调用 move 构造函数和 move 赋值运算符,调用的是 copy 构造函数和 copy 赋值。

1.std::move()

为了解决上面的问题,C++ 11引入了std::move(), std::move 是一个标准库函数,它将其参数(使用 static_cast)转换为右值引用,以便可以调用移动语义。因此,我们可以使用 std::move 将左值转换为右值。

提示:std::move在<utility>头文件中定义

使用std::move后原程序变为:

#include <iostream>
#include <string>
#include <utility> // for std::move

template <typename T>
void mySwapMove(T& a, T& b)
{
	T tmp { std::move(a) }; // invokes move constructor
	a = std::move(b); // invokes move assignment
	b = std::move(tmp); // invokes move assignment
}

int main()
{
	std::string x{ "abc" };
	std::string y{ "de" };

	std::cout << "x: " << x << '\n';
	std::cout << "y: " << y << '\n';

	mySwapMove(x, y);

	std::cout << "x: " << x << '\n';
	std::cout << "y: " << y << '\n';

	return 0;
}

输出结果跟之前一样,但是性能大幅提升。

2.std::move作用

在用左值填充容器的元素(例如 std::vector)时,我们也可以使用 std::move。
另一个例子:

#include <iostream>
#include <string>
#include <utility> // for std::move
#include <vector>

int main()
{
	std::vector<std::string> v;

	// We use std::string because it is movable (std::string_view is not)
	std::string str { "Knock" };

	std::cout << "Copying str\n";
	v.push_back(str); // calls l-value version of push_back, which copies str into the array element

	std::cout << "str: " << str << '\n';
	std::cout << "vector: " << v[0] << '\n';

	std::cout << "\nMoving str\n";

	v.push_back(std::move(str)); // calls r-value version of push_back, which moves str into the array element

	std::cout << "str: " << str << '\n'; // The result of this is indeterminate
	std::cout << "vector:" << v[0] << ' ' << v[1] << '\n';

	return 0;
}

在我的编译器上输出结果如下:

Copying str
str: Knock
vector: Knock

Moving str
str:
vector: Knock Knock

在第一个push_back()中我们传入了左值,所以调用了拷贝构造函数;在第二个push_back()中我们使用std::move将左值转成了右值,调用的是移动语义,第二种方式性能更高。

二、注意事项

1.访问一个被move后的对象合法,但我们不应该对它的值有任何期待

当我们从临时对象中移动值时,被的对象留下什么值并不重要,因为临时对象无论如何都会立即被销毁。但是我们使用 std::move()的左值对象呢?因为我们可以在移动这些对象的值后继续访问它们(例如,在上面的例子中,我们在移动 str 的值后打印它),所以知道它们还剩下什么值是很有用的。

这里有两种思想流派。一种认为,已移出的对象应重置回某种默认/零状态,即对象不再拥有资源。我们在上面看到一个这样的例子,其中 str 已被清除为空字符串。
另外一种认为我们应该做任何最方便的事情,而不是局限于在不方便的情况下必须清除移出的对象。

提示:所以我们在移动后就不应该再依赖这个对象的值。

对于被移动后的对象,可以安全地调用任何不依赖于对象当前值的函数。这意味着我们可以设置或重置对象的值(使用 operator= 或任何类型的 clear()或 reset() 成员函数)。我们还可以测试 moved-from 对象的状态(例如,使用 empty() 查看对象是否有值)。但是,我们应该避免像 operator[] 或 front() 这样的函数(它们返回容器中的第一个元素),因为这些函数依赖于具有元素的容器,而被移动后的对象的值再重新赋值前任何值都有可能。

提示:std::move() 向编译器提示程序不再需要对象的值。不要对超出该点的对象值做出任何假设。在移动当前值后,可以给被移动的对象一个新值(例如使用 operator=)。

总结

本文介绍了使用std::move可以将左值转化为右值,从而扩展了移动构造和移动赋值运算符的使用场景,可以节约不必要的拷贝和销毁释放,让我们的代码更加高效。

  • 22
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值