探索 std::move

C++里通常分为左右值,但标准里更细化

img

这里需要明确右值的概念:

纯右值 prvalue 是没有标识符、不可以取地址的表达式,一般也称之为“临时对象”。

最常见的情况有:

  • 返回非引用类型的表达式,如 x++、x + 1、make_shared(42)
  • 除字符串字面量之外的字面量,如 42、true

C++11 开始,C++ 语言里多了一种引用类型—右值引用。右值引用的形式是 T&&,比左值引用多一个 & 符号。

我们使用右值引用的目的是实现移动,而实现移动的意义是减少运行的开销。在使用容器类的情况下,移动更有意义。

std::move

虽然不能将一个右值引用直接绑定到一个左值上,但我们可以显式地将一个左值转换为对应的右值引用类型。我们还可以通过调用一个名为move的新标准库函数来获得绑定到左值上的右值引用,此函数定义在头文件utility中。

int &&rr3  = std:move(rr1);//ok

move调用告诉编译器:我们有一个左值,但我们希望像一个右值一样处理它。我们必须
认识到,调用move就意味着承诺:除了对rr1赋值或销毁它外,我们将不再使用它。在
调用move之后,我们不能对移后源对象的值做任何假设。

以下是MSVC的std::move源代码:

template <class _Ty>
_NODISCARD constexpr remove_reference_t<_Ty>&& move(_Ty&& _Arg) noexcept { // forward _Arg as movable
    return static_cast<remove_reference_t<_Ty>&&>(_Arg);
}

template <class _Ty>
using remove_reference_t = typename remove_reference<_Ty>::type;

template <class _Ty>
struct remove_reference {
    using type                 = _Ty;
    using _Const_thru_ref_type = const _Ty;
};

template <class _Ty>
struct remove_reference<_Ty&> {
    using type                 = _Ty;
    using _Const_thru_ref_type = const _Ty&;
};

template <class _Ty>
struct remove_reference<_Ty&&> {
    using type                 = _Ty;
    using _Const_thru_ref_type = const _Ty&&;
};

MinGW源代码:

  template<typename _Tp>
    _GLIBCXX_NODISCARD
    constexpr typename std::remove_reference<_Tp>::type&&
    move(_Tp&& __t) noexcept
    { return static_cast<typename std::remove_reference<_Tp>::type&&>(__t); }

  /// remove_reference
  template<typename _Tp>
    struct remove_reference
    { typedef _Tp   type; };

  template<typename _Tp>
    struct remove_reference<_Tp&>
    { typedef _Tp   type; };

  template<typename _Tp>
    struct remove_reference<_Tp&&>
    { typedef _Tp   type; };

为了初学者更方便阅读,简化版:

  template<typename _Tp>
    typename std::remove_reference<_Tp>::type&&
    move(_Tp&& __t)
    { return static_cast<typename std::remove_reference<_Tp>::type&&>(__t); }

  /// remove_reference
  template<typename _Tp>
    struct remove_reference
    { typedef _Tp   type; };

  template<typename _Tp>
    struct remove_reference<_Tp&>
    { typedef _Tp   type; };

  template<typename _Tp>
    struct remove_reference<_Tp&&>
    { typedef _Tp   type; };

这里会出现一个概念,引用坍缩(又称“引用折叠”)。

  • 对于 template foo(T&&) 这样的代码,如果传递过去的参数是左值,T 的推导结果是左值引用;
  • 如果传递过去的参数是右值,T 的推导结果是参数的类型本身。如果 T 是左值引用,那 T&& 的结果仍然是左值引用——即 type&,也就是&& 坍缩成了 type&。
  • 如果 T 是一个实际类型,那 T&& 的结果自然就是一个右值引用。

std::forwardstd::move 一样都是利用引用坍缩机制来实现。它可以实现目标的参数类型不知道,但我们仍然需要能够保持参数的值类别:左值的仍然是左值,右值的仍然是右值。

写个例子看看现象:

class MoveClass {
 public:
	MoveClass()
	{
		cout << "default constructor" << endl;
	}
    //拷贝构造
	MoveClass(const MoveClass& m)
	{
		cout << "copy constructor" << endl;
	}
    //移动构造
	MoveClass(MoveClass&& m)
	{
		cout << "move constructor" << endl;
	}

	MoveClass Get()
	{
		MoveClass tmp;
		//简单返回对象,一般有 NRVO
		return tmp;
	}

	MoveClass GetMove()
	{
		MoveClass tmp;
		//此时使用move 会禁止 NRVO。也就是,用了 std::move 反而妨碍了返回值优化。
		return std::move(tmp);
	}
};
//main.cpp
int main()
{
	MoveClass m;
	std::cout << "1-------------------" << std::endl;
	MoveClass m1(std::move(m));
	std::cout << "2-------------------" << std::endl;
	MoveClass m2(std::move(MoveClass()));

	std::cout << "3-------------------" << std::endl;
	auto r = m.Get();
	std::cout << "4-------------------" << std::endl;
	auto r1 = m.GetMove();

	return 0;
}

输出:

default constructor
1-------------------
move constructor
2-------------------
default constructor
move constructor
3-------------------
default constructor
4-------------------
default constructor
move constructor

m1如果是m1(m)那么会调用拷贝构造,但是这里用了std::move(m)调用了移动构造。

m2同理,先默认构造出一个临时对象,再移动构造。

r,r1解释如下:

在 C++11 之前,返回一个本地对象意味着这个对象会被拷贝,除非编译器发现可以做返回值 优化(named return value optimization,或 NRVO),能把对象直接构造到调用者的栈上。

从 C++11 开始,返回值优化仍可以发生,但在没有返回值优化的情况下,编译器将试图 把本地对象移动出去,而不是拷贝出去。这一行为不需要程序员手工用 std::move 进行干预 ——使用 std::move 对于移动行为没有帮助,反而会影响返回值优化。

std::string str = "Hello";
std::vector<std::string> v;

// 使用 push_back(const T&) 重载,表示我们将带来复制 str 的成本
v.push_back(str);
std::cout << "After copy, str is \"" << str << "\"\n";

// 使用右值引用 push_back(T&&) 重载, 表示不复制字符串;而是str 的内容被移动进 vector
// 这个开销比较低,但也意味着 str 现在可能为空。
v.push_back(std::move(str));
std::cout << "After move, str is \"" << str << "\"\n";

std::cout << "The contents of the vector are \"" << v[0]
                                    << "\", \"" << v[1] << "\"\n";

可能的输出

After copy, str is "Hello"
After move, str is ""
The contents of the vector are "Hello", "Hello"

//TODO:添加std::forward

值类别

The deal with C++14 xvalues
《现代 C++ 编程实战》 吴咏炜

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C++11标准中,引入了线程库来支持多线程编程。std::thread是线程库中的一个类,用于创建和管理线程。std::move是C++中的一个右值引用函数,用于将对象从一个变量转移到另一个变量,同时可以避免对象的拷贝和移动的开销。在多线程编程中,std::thread和std::move是两个非常有用的工具。 std::thread可以在一个独立的执行线程中执行给定的函数。使用std::thread的最常见方式是传递一个函数指针或可调用对象的引用给它的构造函数。当std::thread对象被创建时,它会启动一个线程并运行指定的函数。该构造函数的参数列表可以包括任何给定函数的参数。线程的返回值可以通过std::future<T>对象获得,其中,T是被线程函数返回值的类型。 在多线程编程中,std::move可以用于将线程对象转移到一个新的线程管理器对象中。例如,在程序中,有时需要将一个线程对象转移到一个新的std::thread管理器对象中,以便在不同线程中执行相同的任务。这时可以使用std::move来移动线程对象,避免了拷贝和移动的开销。 同时需要注意的是,当使用std::move将线程对象转移到另一个管理器对象时,应该确保在源线程对象中不再使用该对象,否则可能会导致未定义的行为。因此,当使用std::move在多线程环境中移动对象时,必须小心谨慎。需要仔细考虑线程对象的生命周期,以确保线程的正确执行。 总之,std::thread和std::move是在C++11中引入的非常有用的工具,在多线程编程中可以大大提高程序的性能和效率。同时需要注意在使用时小心谨慎,确保使用正确和安全。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值