【问题标题】:Rvalue references and class design右值引用和类设计
【发布时间】:2012-10-07 05:14:40
【问题描述】:

我试图评估右值引用如何影响类的设计。假设我有一个现有的类,如下所示

class X
{
   string internal;

public:
   void set_data(const char* s)
   {
      internal = s;
   }
..
..
..
//other stuff

};

这个类被另一个模块使用,像这样:

//another module
{

    string configvalue;
    X x;

    //read configvalue from a file and call set 

    ...
    x.set_data(configvalue.c_str());

    //use x to do some magic
    ..
    ...


}

如果有右值引用,那么提供另一个类似这样的成员函数会更好

class X
{
...
...
....
 void set_data(string s)
 {
     internal = std::move(s);
 }
};

这将允许此类的客户端使用移动语义并防止每次使用一组分配/复制操作。这是一个高度炮制的例子,但相同的原则是否适用于所有类设计,而不会破坏“最小接口”范式。

非常感谢任何对此问题的见解?

【问题讨论】:

    标签: c++ interface c++11 rvalue-reference


    【解决方案1】:

    是的,按照您的建议添加 string 重载是个好主意。即使没有右值引用,这样的重载也是一个好主意。否则,给定一个std::string s,要使用它必须:

    x.set_data(s.c_str());
    

    x.set_data(s);
    

    对于X 的客户来说更直观(甚至更高效)。

    作为另一种选择,您可以添加这两个重载:

    void set_data(const string& s) {internal = s;}
    void set_data(string&& s)      {internal = std::move(s);}
    

    这大致相当于您正确建议的单个重载。两次重载解决方案的性能优势非常小。当传递的参数是一个 xvalue(一个已使用 std::move 强制转换的左值)时,单重载解决方案将花费额外的 string 移动构造。但是std::string 的移动构造函数应该非常快,所以这应该没什么大不了的。我只是本着完全公开的精神提及它。

    如果set_data 有多个参数,则“按值”方法变得更具吸引力。例如考虑需要传入两个strings 的情况。您的选择是:

    解决方案 1

    void set_data(string s1, string s2);
    

    解决方案 2

    void set_data(const string&  s1, const string&  s2);
    void set_data(      string&& s1, const string&  s2);
    void set_data(const string&  s1,       string&& s2);
    void set_data(      string&& s1,       string&& s2);
    

    如您所见,解决方案 2 在参数数量上的扩展性很差。

    最后,在任何情况下,您都不应尝试将两种解决方案应用于同一类型:

    不要这样做!

    void set_data(string s)        {internal = std::move(s);}
    void set_data(const string& s) {internal = s;}
    void set_data(string&& s)      {internal = std::move(s);}
    

    这组重载将是模棱两可的。就像在 C++03 中一样,以下两个重载是模棱两可的:

    void set_data(string s)        {internal = std::move(s);}
    void set_data(const string& s) {internal = s;}
    

    永远不要用引用重载按值,无论是左值引用还是右值引用。

    【讨论】:

    • void set_data(const string& s) {internal = s;} void set_data(string&& s) {internal = std::move(s);},什么时候第一次被调用,什么时候第二次调用?
    • 第一个版本更喜欢绑定到左值...第二个版本更喜欢绑定到右值以及包裹在std::move中的左值。
    • 关于您的多个参数传递 - 是的,它的扩展性很差 - 但这不应该是选择“按值”方法的原因。它可以通过将参数存储在单个配置对象中轻松解决(并且可能应该出于设计考虑)
    【解决方案2】:

    我认为没有理由同时将void set_data(const char* s)void set_data(string s) 作为界面的一部分。这将产生歧义并且容易产生副作用。此外,您仍然在调用set_data(string s) 时按值传递参数。相反,我建议定义以下 2 个函数:

    void set_data(const string &s);
    void set_data(string &&s);
    

    这样你可以有 2 个实现,第一个将深度复制你的字符串,第二个可以窃取字符串的内部,因为它是一个 rvalue(确保将它保持在定义的状态,以便析构函数能够毫无问题地销毁它 - 详情请参阅http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n2027.html#Move_Semantics)。

    第二个版本将在 rvalue string 参数或参数被强制为 rvalue 时自动调用,例如 std::move

    如果您也想使用按值选项,可以使用此 API 的 rvalue 版本与字符串复制构造函数:set_data(string(str))

    【讨论】:

    • @HowardHinnant 没有。你有吗?
    • 是的。当我实现你的前两个set_data 并使用右值string 调用时,我的编译器抱怨有歧义。当我添加您的第三个 set_data 重载时,在使用左值 string 调用时也会出现歧义。
    • a,是的 - 因为价值。它应该被删除,如果需要,可以通过其他两个和某种克隆方法来实现相同的结果,例如
    • @HowardHinnant a,是的,你是对的 - 因为按值。它应该被删除,如果需要,可以通过其他两个和某种克隆方法来实现相同的结果,例如。我已经编辑了答案。
    猜你喜欢
    • 2011-07-13
    • 2015-12-23
    • 1970-01-01
    • 2016-04-26
    • 1970-01-01
    • 2016-04-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多