提供移动参数的构造函数的惯用方式

Idiomatic way of providing constructors that move their arguments

假设我有以下 class:

#include <vector>
class Foo
{
public:
    Foo(const std::vector<int> & a, const std::vector<int> & b)
        : a{ a }, b{ b } {}
private:
    std::vector<int> a, b;
};

但现在我想考虑构造函数的调用者可能将临时对象传递给它的情况,我想将这些临时对象正确地移动到 ab.

现在我真的必须再添加 3 个构造函数,其中 1 个具有 a 作为右值引用,其中 1 个具有 b 作为右值引用,而 1 个仅具有右值引用参数?

当然,这个问题可以概括为任何数量的值得移动的参数,所需构造函数的数量将是 arguments^2 2^arguments.

这个问题也适用于所有函数。

执行此操作的惯用方法是什么?还是我在这里完全遗漏了一些重要的东西?

通常的方法是按值传递,然后从参数移动构造成员:

Foo(std::vector<int> a, std::vector<int> b)
    : a{ std::move(a) },
      b{ std::move(b) }
{}

如果需要副本,它将由调用者创建,然后从中移出以构造成员。如果调用者传递一个临时值(或其他右值),则不会进行任何复制,只会进行一次移动。

对于没有高效移动构造函数的参数,接受对 const 的引用会稍微高效一些,我会保留这一点。

None 如果函数不需要传递值的 copy - 如果不修改则继续使用 const ref值并且不需要它在函数执行结束后继续存在。就个人而言,我在我的构造函数中大量使用按值传递和移动,但很少在我的其他函数中使用。

真的,如果搬家建筑很便宜,你应该按价值计算。

这导致在每种情况下都比理想情况多走 1 步。

但如果你真的必须避免这种情况,你可以这样做:

template<class T>
struct sink_of {
  void const* ptr = 0;
  T(*fn)(void const*) = 0;
  sink_of(T&& t):
    ptr( std::addressof(t) ),
    fn([](void const*ptr)->T{
      return std::move(*(T*)(ptr));
    })
  {}
  sink_of(T const& t):
    ptr( std::addressof(t) ),
    fn([](void const*ptr)->T{
      return *(T*)(ptr);
    })
  {}
  operator T() const&& {
    return fn(ptr);
  }
};

它使用 RVO/elision 来避免以一堆基于指针的开销和类型擦除为代价的额外移动。

Here 是一些测试代码,证明

test( noisy nin ):n(std::move(nin)) {}
test( sink_of<noisy> nin ):n(std::move(nin)) {}

恰好与 noisy.

的 1 个移动构造不同

"perfect"版本

test( noisy const& nin ):n(nin) {}
test( noisy && nin ):n(std::move(nin)) {}

template<class Noisy, std::enable_if_t<std::is_same<noisy, std::decay_t<Noisy>>{}, int> = 0 >
test( Noisy && nin ):n(std::forward<Noisy>(nin)) {}

sink_of 版本的 copy/moves 数量相同。

noisy 是一种打印有关 moves/copies 它参与的信息的类型,因此您可以看到通过省略优化了哪些内容)

只有当多余的 move 需要消除时才值得 。对于 vector 它不是。

另外,如果你有一个 "true temporary" 你通过了,按价值计算的和 sink_of 或 "perfect" 一样好。