我应该始终继续使用 `sink` 构造函数或 setter 参数吗?

问题描述 投票:0回答:2
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
更好/更有效率的情况?


那么琐碎的 setter 呢?我应该使用以下习语还是传递

const std::string&

struct TestSetter {
    std::string str;
    void setStr(std::string mStr) { str = std::move(str); }
};
c++ c++11 constructor copy move-semantics
2个回答
22
投票

简单的答案是:是的。


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

来自临时

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

一个限制:没有有效移动构造函数的类(例如

std::array<T, N>
),因为这样你就做了两个副本而不是一个。

来自左值(或 const 临时值,但谁会这样做......)

  • 如果您通过 const-ref 获取参数,则不会发生任何事情,然后您复制它(无法从中移动),从而创建一个副本。
  • 如果按值获取参数,则将其复制到参数中,然后从中移动,从而创建一个副本。

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

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

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


11
投票

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

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

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

来自临时(案例

// 1

仅调用一个移动构造函数

str

来自左值(案例

// 2

仅调用一个复制构造函数

str

可能没有比这更好的了。

但是等等,还有更多:

caller 端不会生成额外的代码!复制或移动构造函数(可能是内联的,也可能不是)的调用现在可以存在于被调用函数的实现中(此处:

Test::Test
),因此只需要该代码的单个副本。如果使用按值参数传递,则调用者负责生成传递给函数的对象。这可能会在大型项目中增加,如果可能的话我会尽量避免。

© www.soinside.com 2019 - 2024. All rights reserved.