注意:正如 sellibitze 所指出的,我不是最新的右值引用,因此我提出的方法包含错误,请阅读他的回答以了解错误。
我正在阅读 Linus' rant 中的一篇昨天有(某处)反对运算符重载的咆哮。
提示似乎是,如果你有一个 S
类型的对象,那么:
S a = b + c + d + e;
可能涉及很多临时对象。
在 C++03 中,我们有复制省略来防止这种情况:
S a = ((b + c) + d) + e;
我希望最后的 ... + e
得到优化,但我想知道有多少临时文件是用用户定义的 operator+
创建的。
线程中有人建议使用表达式模板来处理这个问题。
现在,这个线程可以追溯到 2007 年,但现在当我们想到消除临时变量时,我们会想到 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
。
最佳答案
如果您正在考虑一个自定义的、支持移动的字符串类,那么利用参数值类别的每个组合的正确方法是:
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);
函数返回一个 prvalue 而不是 xvalue。返回 xvalues 通常是一件非常危险的事情——std::move 和 std::forward 是明显的异常(exception)。如果你要返回一个右值引用,你会破坏如下代码:
for (char c : my_string + other_string) {
//...
}
这个循环的行为(根据 N3092 中的 6.5.4/1)就好像代码是:
auto&& range = my_string + other_string;
这反过来会导致悬空引用。临时对象的生命周期没有延长,因为你的 operator+ 没有返回一个 prvalue。按值返回对象是完全没问题的。它会创建临时对象,但这些对象是右值,因此我们可以窃取它们的资源以使其非常有效。
其次,您的代码也不应编译,原因与无法编译的原因相同:
int&& foo(int&& x) { return x; }
在函数体内 x 是一个左值,您不能用左值表达式初始化“返回值”(在本例中为右值引用)。因此,您需要一个显式转换。
第三,您缺少 const&+const& 重载。如果您的两个参数都是左值,编译器将不会在您的情况下找到可用的 operator+。
如果你不想重载那么多,你也可以这样写:
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
}
请参阅 David Abraham 的文章 Want Speed? Pass by Value . 但这些简单的运算符不会那么有效:
S x = a + (b + (c + d));
此处运算符的左侧始终是左值。由于 operator+ 按值取其左侧,这导致许多拷贝。上面的四个重载也完美地处理了这个例子。
我已经有一段时间没有读到 Linus 的旧话了。如果他提示关于 std::string 的不必要拷贝,那么这个提示在 C++0x 中不再有效,但在以前几乎无效。您可以在 C++03 中高效地连接多个字符串:
S result = a;
result += b;
result += c;
result += d;
但在 C++0x 中,您还可以使用 operator+ 和 std::move。这也将非常有效。
我实际上查看了 Git 源代码及其字符串管理 (strbuf.h)。它看起来经过深思熟虑。除了分离/附加功能外,您可以使用启用移动的 std::string 获得相同的东西,其明显优势是资源由类本身自动管理,而不是需要记住调用正确函数的用户正确的时间(strbuf_init、strbuf_release)。
关于c++ - 消除运算符重载中的临时变量,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3792856/