我应该总是在“sink”构造函数或setter参数上移动

Should I always move on `sink` constructor or setter arguments?

本文关键字:setter 参数 移动 构造函数 sink 我应该      更新时间:2023-10-16
struct TestConstRef {
    std::string str;
    Test(const std::string& mStr) : str{mStr} { }
};
struct TestMove {
    std::string str;
    Test(std::string mStr) : str{std::move(mStr)} { }
};

看完 GoingNative 2013 后,我明白了 sink 参数应该始终按值传递并随std::move移动。TestMove::ctor应用这个成语的正确方法吗?有没有TestConstRef::ctor更好/更有效的情况?


琐碎的二传手呢?我应该使用以下成语还是传递const std::string&

struct TestSetter {
    std::string str;
    void setStr(std::string mStr) { str = std::move(str); }
};

简单的答案是:是的。


原因也很简单,如果您按值存储,则可能需要移动(从临时)或复制(从l值)。让我们看看在这两种情况下,以两种方式会发生什么。

从临时

  • 如果你通过 const-ref 获取参数,则临时参数绑定到 const-ref 并且无法再次移动,因此您最终会制作一个(无用的)副本。
  • 如果您按值获取参数,则从临时(移动)初始化值,然后您自己从参数中移动,因此不会进行复制。
一个

限制:一个没有有效移动构造函数(如std::array<T, N>)的类,因为那时你做了两个副本而不是一个。

从 l 值(或常量临时,但谁会这样做......

  • 如果你通过 const-ref 获取参数,那里什么都不会发生,然后你复制它(不能从它移动),因此制作了一个副本。
  • 如果按值获取参数,则将其复制到参数中,然后从参数中移动,从而创建单个副本。

一个限制:相同的...移动类似于复制的类。

因此,简单的答案是,在大多数情况下,通过使用接收器可以避免不必要的副本(用移动替换它们)。

唯一的限制是移动构造函数

与复制构造函数一样昂贵(或接近昂贵)的类;在这种情况下,有两个移动而不是一个副本是"最糟糕的"。值得庆幸的是,这样的类很少见(数组是一种情况)。

有点晚了,因为这个问题已经有一个公认的答案,但无论如何......这是一个替代方案:

struct Test {
    std::string str;
    Test(std::string&& mStr) : str{std::move(mStr)} { } // 1
    Test(const std::string& mStr) : str{mStr} { } // 2
};

为什么会更好?考虑两种情况:

从临时(案例// 1

str只需要一个移动构造函数。

从 l 值(案例 // 2

str只需要一个复制构造函数。

它可能不会比这更好。

但是等等,还有更多:

调用方不会生成额外的代码!复制或移动构造函数(可能是内联的,也可能不是内联的)现在可以存在于被调用函数的实现中(这里:Test::Test),因此只需要该代码的单个副本。如果使用按值参数传递,则调用方负责生成传递给函数的对象。这可能会在大型项目中加起来,如果可能的话,我会尽量避免它。