是否应将C++ 11中的所有/大多数setter函数编写为接受通用引用的函数模板?

And*_*owl 63 c++ move-semantics perfect-forwarding c++11 universal-reference

考虑一个X具有N成员变量的类,每个类都有一些可复制可移动的类型,以及N相应的setter函数.在C++ 98中,定义X可能如下所示:

class X
{
public:
    void set_a(A const& a) { _a = a; }
    void set_b(B const& b) { _b = b; }
    ...
private:
    A _a;
    B _b;
    ...
};
Run Code Online (Sandbox Code Playgroud)

X上面类的setter函数可以绑定到左值和右值参数.根据实际的说法,这可能导致创建临时的,并最终导致的分配副本; 因此,此设计不支持不可复制的类型.

使用C++ 11,我们可以移动语义,完美转发和通用引用(Scott Meyers的术语),通过以下方式重写它们,可以更有效和广泛地使用setter函数:

class X
{
public:
    template<typename T>
    void set_a(T&& a) { _a = std::forward<T>(a); }

    template<typename T>
    void set_b(T&& b) { _b = std::forward<T>(b); }
    ...
private:
    A _a;
    B _b;
    ...
};
Run Code Online (Sandbox Code Playgroud)

通用引用通常可以绑定到const/非const,volatile/非volatile和任何可转换类型,从而避免创建临时值并直接传递值operator =.不可复印,可移动的类型现在支持.可能通过static_assert或通过可以消除不希望的结合std::enable_if.

所以我的问题是:作为一个设计指南,C++ 11中的所有(比如说,大多数)setter函数是否应该被编写为接受通用引用的函数模板?

除了更麻烦的语法和在这些setter函数中编写代码时使用类似Intellisense的辅助工具的不可能性时,假设原则" 写入setter函数作为函数模板尽可能接受通用引用 "还有任何相关的缺点吗?

Pup*_*ppy 37

你知道A类和B类,所以你知道它们是否可移动,以及最终是否需要这种设计.对于类似的事情std::string,除非您知道这里存在性能问题,否则更改现有代码会浪费时间.如果你正在处理auto_ptr,那么是时候把它撕掉并使用了unique_ptr.

如果您不知道任何更具体的内容,现在通常更喜欢按值获取参数 - 例如

void set_a(A a) { _a = std::move(a); }
Run Code Online (Sandbox Code Playgroud)

这允许使用任何构造器A而不需要除可移动性之外的任何东西并且提供相对直观的界面.

  • 这是假的,对于像std :: string这样的东西,按值接受参数会导致在使用左值调用函数时保证分配.通过const引用传递,实际工作发生在函数体内的赋值中,因为实际工作发生在复制赋值而不是复制构造中,如果足够大,则可以重用预分配空间.你可以在循环中对此进行基准测试,你会发现const ref压缩了左值调用的值.如果你想要rvalues的速度提升,你应该实现一个rvalue重载. (15认同)
  • 好的,我会接受这个答案并采取我正在寻找的设计原则如下:"*对于像示例中所示的setter函数,如果类型是可移动的并且移动构造的性能不是问题,那么请按值的参数;否则,使用通用引用并通过`std :: enable_if`或`static_assert`排除不需要的绑定;在任何情况下,避免使用const引用参数(再次:对于setter函数,如示例中的那些)*".谢谢 (6认同)
  • @Mike复制构造函数总是触发内存分配.如果新内容明显大于旧内容,则复制分配仅触发内存分配.即使假设"紧密"内存分配,对于N个明显大小的字符串的列表,对每个通过复制构造的设置是N分配.通过复制分配是O(logN). (3认同)