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

C++ 是否所有/大多数setter函数都应使用C++;11是否编写为接受通用引用的函数模板?,c++,c++11,move-semantics,perfect-forwarding,universal-reference,C++,C++11,Move Semantics,Perfect Forwarding,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; ... }; 上面类X的Setter函数可以绑定到左值和右值参数。根

考虑一个类
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;
    ...
};
上面类
X
的Setter函数可以绑定到左值和右值参数。根据实际参数,这可能导致创建临时文件,并最终导致副本分配;因此,此设计不支持不可复制类型

在C++11中,我们有移动语义、完美转发和通用引用(Scott Meyers的术语),通过将setter函数重写为以下方式,可以更高效、更广泛地使用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;
    ...
};
X类
{
公众:
模板
无效集{u a=std::forward(a);}
模板
无效集{u b=std::forward(b);}
...
私人:
A_A,;
B_B;
...
};
通用引用可以绑定到
常量
/non-
常量
volatile
/non-
volatile
,也可以绑定到一般的任何可转换类型,避免创建临时变量并将值直接传递给
操作符=
。现在支持不可复制、可移动的类型。可能不需要的绑定可以通过
static\u assert
std::enable\u(如果
)消除

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


除了更麻烦的语法和在那些setter函数中编写代码时无法使用类似Intellisense的辅助工具之外,假设的原则“将setter函数编写为函数模板,尽可能接受通用引用”是否存在任何相关的缺点,因此,您知道它们是否可移动,以及这种设计是否最终是必要的。对于像
std::string
这样的东西,除非您知道这里有性能问题,否则更改现有代码是浪费时间的。如果您正在处理
auto_ptr
,那么是时候将其删除并使用
unique_ptr

如果您不知道任何更具体的内容,例如

void set_a(A a) { _a = std::move(a); }

这允许使用
A
的任何构造函数,而不需要除可移动性以外的任何东西,并提供了一个相对直观的界面。

@sehe:这些setter函数有意简化,这里可能有比赋值更重要的事情。@sehe:也许将来的实现实际上不会有
a
字段,但仍会通过存储
a
的指定实例中所需的任何属性,在逻辑上实现
set\u a
。或者在将来,该字段的值可能不会与所有其他数据成员正交,因此
set\u a
可能也会更新其他内容。我知道,雅格尼,但是如果这个类被调用为
URL
,那么我就不一定要提交给
协议
数据成员,类型为
字符串
,即使我愿意提交给总是有
设置协议
成员函数。@sehe:我不明白你的意思。我可能有一个成员变量,它需要非平凡的设置/获取(我的示例只是简化,赋值可能只是其中的一部分)。为什么我不应该使用getter/setter函数呢?当然,我没有在我的示例中显示getter函数,因为它们与我要问的问题无关,但这并不意味着这些属性是只写的。@sehe为什么(在地球上)堆栈溢出上的太简单的示例和太复杂的示例一样糟糕?我想你完全明白了。当我们想在getter/setter后面隐藏一个成员时,我们应该如何实现它?OP确实提供了一个小例子。句号。@sehe例如,将来触发
a_changed()
事件怎么样?或者调试属性的更改…我明白你的意思,但是如果使用右值调用
set\u a
,这将导致不必要的移动,不是吗?好的,我接受这个答案,并采用我所寻求的设计原则,如下所示:“对于示例中所示的setter函数,如果类型是可移动的,并且移动构造的性能不是问题,则按值获取参数;否则,使用通用引用并通过
std::enable_if
static_assert
排除不需要的绑定;在任何情况下,都应避免采用const ref的参数(同样,对于示例中的setter函数)“.Thank yout这是错误的,对于std::string之类的东西,只要使用左值调用函数,按值接受参数就会导致有保证的分配。而通过常量引用传递,实际工作发生在函数体内部的赋值中,因为实际工作发生在复制赋值中而不是复制构造中,如果足够大,预分配的空间可以重用。您可以在一个循环中对此进行基准测试,您将看到constref为左值调用压碎值。如果您想提高右值的速度,应该实现右值重载。我怀疑“按值传递”+
std::move()
是否是解决此问题的正确设计。如果是这样,为什么标准库中的许多类同时提供
T常量和
T&&
重载?为什么我们首先需要两个重载?@Mike复制构造函数总是触发内存分配。仅当新内容明显大于旧内容时,复制分配才会触发内存分配。即使假设内存分配“紧张”,对于N个大小不同的字符串的列表,设置