消除操作员超载的临时性

时间:2010-09-25 07:04:43

标签: c++ operator-overloading c++11

注意:正如sellibitze所指出的那样我对rvalues引用并不是最新的,因此我提出的方法包含错误,请阅读他的anwser以了解哪些错误。

昨天我正在阅读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无效。

1 个答案:

答案 0 :(得分:4)

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

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是明显的例外。如果您要返回右值引用,则会破坏以下代码:

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

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

auto&& range = my_string + other_string;

这反过来导致悬挂参考。临时对象的生命周期未延长,因为您的operator +未返回 prvalue 。按值返回对象是完全正常的。它会创建临时对象,但这些对象是rvalues,所以我们可以窃取他们的资源以使其非常有效。

其次,您的代码也应该因为无法编译的原因而编译:

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

在函数体内 x 是左值,你不能用左值表达式初始化“返回值”(在本例中是右值引用)。所以,你需要一个明确的演员。

第三,你错过了一个const& + const&超载。如果你的两个参数都是左值,编译器将无法在你的情况下找到一个可用的operator +。

如果你不想要这么多重载,你也可以写:

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

我故意不写return value+=x;因为这个运算符可能会返回一个左值引用,这会导致复制构造返回值。在我写的两行中,返回值将从 value。

构造
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)。看起来很好。除了分离/附加功能之外,你可以使用支持move的std :: string获得相同的功能,其明显优势在于它自动由类本身管理的资源,而不是需要记住调用正确函数的用户正确的时间(strbuf_init,strbuf_release)。