在工作中我们有一个带有昂贵构造函数的类,所以我们希望尽可能少地调用它。我们仔细研究了它的用法并尝试使代码更加友好,所以说。
然而,我们在g ++编译器中发现了一个奇怪的地方,我们不明白发生了什么。
请考虑operator +
的两个实现const Imaginary Imaginary::operator+(const Imaginary& rhs) const
{
Imaginary tmp(*this);
tmp.append(rhs);
return tmp;
}
和
const Imaginary Imaginary::operator+(const Imaginary& rhs) const
{
return Imaginary(*this).append(rhs);
}
我已将打印输出放在各种构造函数中,并使用以下小程序
int main(int argc, char* argv[])
{
Imaginary x(1, 1);
Imaginary y(2, 1);
Imaginary c = x + y;
return 0;
}
我使用operator +
的第一个实现打印出来int/int ctor
int/int ctor
Copy ctor
当使用operator +的第二个变体时,我得到以下内容
int/int ctor
int/int ctor
Copy ctor
Copy ctor
在这里我们看到g ++能够在一种情况下优化掉一次对复制构造函数的调用而不是后一种情况,令我惊讶的是,它设法用更笨拙的实现了保存到临时。
现在我可以更多地了解它,如果它是另一种方式,但显然它不是 现在我希望也许你可以在这个问题上启发我。
我应该补充一点,当我们添加--no-elide-constructors作为g ++的标志时 我得到以下打印输出
int/int ctor
int/int ctor
Copy ctor
Copy ctor
Copy ctor
问候,马蒂亚斯
答案 0 :(得分:5)
如果编译器无法内联append
,则无法确定返回值是否为目标对象。然后它不知道临时被返回,并且无法在原地构建它。
您的行为与:
相同Imaginary tmp(*this);
return tmp.append(rhs);
如果append
的返回值对编译器不透明(在另一个编译单元中定义),则会阻止优化。
答案 1 :(得分:0)
例如:
string foo() {string tmp; return tmp;} // Same type, uses NRVO or automatic move.
string foo() {const string& tmp = "bar"; return tmp;} // Types differ, no NRVO, nor automatic move.
string foo() {string tmp; string& ref = tmp; return ref;} // Types differ, no NRVO, nor automatic move.
string foo() {string tmp; return (string&) tmp;} // Types differ, no NRVO, nor automatic move.
(参见http://coliru.stacked-crooked.com/a/79e79e5bb0350584)
我猜append
会将引用返回给Imaginary,而Imaginary&
与Imaginary
的类型不同,这会阻止(N)RVO。< / p>