正如这个wiki页面所说(代码如下所示),返回值优化是C ++编译器允许的,但仍取决于实现。为了降低复制成本,建议手动优化它(将函数对象分配给引用,如const C& obj = f();
),还是让编译器在实践中进行优化?
#include <iostream>
struct C {
C() {}
C(const C&) { std::cout << "A copy was made.\n"; }
};
C f() {
return C();
}
int main() {
std::cout << "Hello World!\n";
C obj = f();
}
编辑:将更改更新为const引用。
答案 0 :(得分:3)
你不能(便携地)使用临时返回值初始化非const引用,因此当然不建议使用。
使用它来初始化const引用对于是否可以省略返回表达式的值的复制/移动不会有任何影响;虽然它会消除用于从返回值初始化变量的名义副本/移动,但是否可能已经省略了。当然,这与初始化(非参考)变量不同,因为您无法对其进行修改。
在实践中,任何具有合适优化器的编译器都会在任何允许的地方移除副本并移动。如果你没有使用体面的优化器,那么无论如何你都不能期望体面的表现。
答案 1 :(得分:0)
要手动确保您没有获得任何冗余的对象副本,您需要做的比目前为止要多得多。早些时候我回答说这不可能,但我错了。此外,使用const&
可能会禁止对您希望允许的返回值执行某些操作。这就是我要做的事情如果你需要手动进行优化:
#include <iostream>
struct S {
S()
{ std::cout << "default constructor\n"; }
S(const S &)
{ std::cout << "copy constructor\n"; }
S(S &&)
{ std::cout << "move constructor\n"; }
~S()
{ std::cout << "destructor\n"; }
};
S f() { return {}; }
int main() {
auto&&s = f();
std::cout << "main\n";
}
这会打印&#34;默认构造函数&#34;,然后是&#34; main&#34;,然后&#34;析构函数&#34;。无论是否进行任何复制省略,这都是输出。在main
内,s
是一个命名引用,因此它是一个左值,并且不是const
- 限定的。你可以用它来做任何事情。
鉴于在这些情况下很容易避免依赖复制省略,只要你从一开始就注意它,可能值得你的努力如果您必须担心其他编译器不执行复制省略。大多数编译器都能够做到这一点,而且如果编译器没有,那么它很可能会产生其他更大的问题,所以有一个很好的理由可以不用担心它。
然而,与此同时,复制省略有些不可靠:即使是当前的优化编译器也不会总是执行它,只是因为可能存在复制省略有意义的极端情况,但不是标准允许或特定实施不可能。强迫自己编写不依赖于复制省略的代码意味着你不能陷入困境。
尽管如此,仍然有一些情况下复制省略只能通过优化编译器来实际消除,所以你可能别无选择,只能依赖它:
假设我们将void m();
添加到S
的定义中。假设我们现在将f
编辑为
S f() {
S s;
s.m();
return s;
}
更难以重写为不保证冗余副本的表单。但与此同时,副本 是不必要的,因为很容易从GCC(也可能是其他编译器)这一事实中确定,默认情况下不会制作副本。
我的最后结论是,对于不能执行RVO的编译器来说,它可能不值得优化,但值得仔细思考究竟是什么使它工作,并以这样的方式编写代码仍然不仅可能,而且成为编译器很可能会做的事情。