代码之家  ›  专栏  ›  技术社区  ›  Matthieu M.

消除运算符重载中的临时变量

  •  4
  • Matthieu M.  · 技术社区  · 14 年前

    注: 正如sellibitze所指出的,我对右值的引用不是最新的,因此我建议的方法包含错误,请阅读他的anwser以了解其中的错误。

    Linus' rant 昨天,有人(某处)怒斥操作员超载。

    抱怨似乎是如果你有一个类型的对象 S 然后:

    S a = b + c + d + e;
    

    可能涉及很多临时人员。

    在C++ 03中,我们有复制删除以防止:

    S a = ((b + c) + d) + e;
    

    我希望最后 ... + e 是优化的,但我想知道有多少临时创建了用户定义的 operator+

    Move .

    所以我在想我们应该写的重载操作符集不是为了消除临时操作符,而是为了限制它们的构建成本(窃取资源)。

    S&& operator+(S&& lhs, S const& rhs) { return lhs += rhs; }
    S&& operator+(S const& lhs, S&& rhs) { return rhs += lhs; } // *
    S&& operator+(S&& lhs, S&& rhs) { return lhs += rhs; }
    

    *:此实现假定可交换性,但不适用于臭名昭著的 string .

    1 回复  |  直到 4 年前
        1
  •  4
  •   sellibitze    14 年前

    如果您考虑的是一个自定义的、支持移动的字符串类,那么利用参数值类别的每个组合的正确方法是:

    S operator+(S const& lhs, S const& rhs);
    S operator+(S     && lhs, S const& rhs);
    S operator+(S const& lhs, S     && rhs);
    S operator+(S     && lhs, S     && rhs);
    

    pr值 而不是 X值 . 返回 X值 通常是非常危险的事情–std::move和std::forward是明显的例外。如果要返回一个rvalue引用,则需要按如下方式分解代码:

    for (char c : my_string + other_string) {
       //...
    }
    

    此循环的行为(根据N3092中的6.5.4/1)就像代码是:

    auto&& range = my_string + other_string;
    

    这反过来又会导致一个悬空引用。临时对象的生存期不会延长,因为操作符+不返回 pr值 . 按值返回对象非常好。它将创建临时对象,但这些对象是r值,因此我们可以窃取它们的资源以使其非常有效。

    其次,您的代码也不应该编译,原因与它不编译的原因相同:

    int&& foo(int&& x) { return x; }
    

    函数体内部 是左值,不能用左值表达式初始化“return value”(在本例中是rvalue引用)。所以,你需要一个明确的演员阵容。

    第三,缺少const&+const&重载。如果两个参数都是左值,编译器将无法在您的情况下找到可用的运算符+。

    如果不希望有这么多重载,还可以编写:

    S operator+(S value, S const& x)
    {
       value += x;
       return value;
    }
    

    我故意不写 return value+=x; 价值观。

    S x = a + b + c + d;
    

    至少这种情况非常有效,因为即使编译器无法删除副本,也不会涉及不必要的复制—这要感谢支持移动的字符串类。实际上,使用一个类类似STD::string,可以利用它的快速交换成员函数,并使它在C++ 03中有效,同时也提供了一个合理的智能编译器(如GCC):

    S operator+(S value, S const& x) // pass-by-value to exploit copy elisions
    {
       S result;
       result.swap(value);
       result += x;
       return result; // NRVO applicable
    }
    

    见大卫亚伯拉罕的文章 Want Speed? Pass by Value 但这些简单的运算符在以下情况下不会那么有效:

    S x = a + (b + (c + d));
    

    在这里,运算符的左侧始终是左值。由于运算符+按值取其左侧,因此会产生许多副本。上面的四个重载也完美地处理了这个示例。

    可以 在C++ 03中有效地连接多个字符串:

    S result = a;
    result += b;
    result += c;
    result += d;
    

    我实际上查看了Git源代码及其字符串管理(strbuf.h)。它看起来经过深思熟虑。除了detach/attach特性之外,使用支持移动的std::string也可以得到同样的效果,它的明显优点是它自动管理的资源由类本身来管理,而不是需要记住在正确的时间调用正确函数的用户(strbuf_init,strbuf_release)。