C++ 我应该一直使用'sink'构造函数还是setter参数?

C++ 我应该一直使用'sink'构造函数还是setter参数?,c++,c++11,constructor,copy,move,C++,C++11,Constructor,Copy,Move,在观看GoingNative 2013之后,我明白了sink参数应该始终按值传递,并使用std::move移动。TestMove::ctor是应用此习惯用法的正确方法吗?是否存在TestConstRef::ctor更好/更高效的情况 那么琐碎的setter呢?我应该使用以下习惯用法还是传递一个常量std::string& struct TestConstRef { std::string str; Test(const std::string& mStr) : str{

在观看GoingNative 2013之后,我明白了sink参数应该始终按值传递,并使用
std::move
移动。
TestMove::ctor
是应用此习惯用法的正确方法吗?是否存在
TestConstRef::ctor
更好/更高效的情况


那么琐碎的setter呢?我应该使用以下习惯用法还是传递一个
常量std::string&

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)} { }
};

答案很简单:是的


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

来自临时的

  • 如果按const-ref接受参数,则临时变量将绑定到const-ref,并且不能再次从中移动,因此最终会生成一个(无用的)副本
  • 如果按值获取参数,则该值将从临时(移动)中初始化,然后您自己从参数中移动,因此不会复制
一个限制:没有有效的移动构造函数的类(例如
std::array
),因为这样做了两个副本而不是一个

从一个l值开始(或const-temporary,但谁会这么做…)

  • 如果您通过const ref获取参数,则那里不会发生任何事情,然后您复制了它(无法从中移动),因此创建了一个副本
  • 如果按值获取参数,则将其复制到参数中,然后再从参数中移动,从而生成单个副本
一个限制:相同的。。。移动类似于复制的类

因此,简单的答案是,在大多数情况下,通过使用接收器,您可以避免不必要的拷贝(用移动取代它们)


唯一的限制是移动构造函数与复制构造函数一样昂贵(或接近昂贵)的类;在这种情况下,两次移动而不是一次复制是“最糟糕的”。谢天谢地,这样的类很少(数组就是一种情况)。

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

struct TestSetter {
    std::string str;
    void setStr(std::string mStr) { str = std::move(str); }
};
为什么这样更好?考虑两种情况:

来自临时(案例
//1

对于
str
,只调用一个移动构造函数

来自l值(大小写为
//2

对于
str
,只调用一个副本构造函数

可能没有比这更好的了

但是等等,还有更多:


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

这种说法对我来说似乎是可疑的。通过
const&
然后初始化将调用单个副本构造函数。按值传递和移动将调用一个复制构造函数,后跟一个移动赋值运算符。@Yuushi:一般来说,大多数类的移动构造函数几乎是自由的(相当于交换)。此外,您忘记了从临时变量(或从移动的变量)初始化参数的情况。@MatthieuM。我意识到move构造函数通常几乎是免费的。但是,如果您是从临时/移动自变量进行初始化,为什么不将其声明为显式接受右值引用?@Yuushi则它不适用于其他任何情况。当然,您可以重载,但这会增加额外的代码(即使您没有键入两次,也会导致与过度内联或模板膨胀相同的问题)。只是为了节省一个动作,这通常和交一个参考资料一样便宜(也许它需要两个字而不是一个字,但这就像一个时钟周期)。谢谢你明确的回答!这也适用于琐碎的setter吗?(原文的第二个例子,稍后编辑)“是”是哪个问题的答案?:)@VittorioRomeo:任何复制参数的方法,无论是构造函数、setter还是破坏性计算。@MatthieuM。你分析过这个吗?在搜索之前,我自己也考虑过这个问题,在我测试过的每一个实例中,在g++4.8.1和clang++3.4上,传递值的速度总是比较慢。我使用一个字符串成员进行了测试,在通过引用传递给setter的情况下,该成员已经分配了足够的空间(非空字符串),传递值的速度大大减慢。这都是传递的左值,我还没有看右值。@特洛伊:我怀疑这是因为成员分配了足够的空间,赋值运算符通过在没有分配的情况下覆盖现有字符串来利用它。另一方面,当您使用“按值传递”习惯用法时,制作的副本需要分配新的存储空间;内存分配并不便宜(特别是在使用默认分配器时)。事实上,我没有考虑过在setter的情况下,可以通过复制赋值操作符进行复制,这与复制构造函数具有不同的动态性。据我所知,这是性能和代码生成方面最好的方法,但需要一个额外的构造函数-对吗?@VittorioRomeo:是的,这就是缺点-您总是需要两个重载。如果一个方法有多个参数,这将成为一个负担,它值得考虑的是返回值的参数。所需的CTOR数量可能会呈指数增长。。。有没有解决这个问题的建议?我同意,这看起来确实像是语言缺陷。如果能够像使用templat时那样拥有一个构造函数,那就太好了
struct Test {
    std::string str;
    Test(std::string&& mStr) : str{std::move(mStr)} { } // 1
    Test(const std::string& mStr) : str{mStr} { } // 2
};