我习惯于优化受到限制,以至于它们无法改变可观察到的行为。
这是对的。作为一般规则 - 称为as-if规则 - 如果更改不可观察,编译器可以更改代码。
此限制似乎不适用于 RVO。
是的。OP 中引用的子句为 as-if规则提供了一个例外,并允许省略复制构造,即使它有副作用。请注意,RVO 只是复制省略的一种情况(C++11 12.8/31 中的第一个要点)。
我是否需要担心标准中提到的副作用?
如果复制构造函数具有副作用,例如执行复制省略会导致问题,那么您应该重新考虑设计。如果这不是您的代码,您可能应该考虑一个更好的选择。
作为程序员,我需要做什么(或不做什么)才能执行此优化?
基本上,如果可能,返回一个与函数返回类型具有相同 cv 非限定类型的局部变量(或临时变量)。这允许 RVO,但不强制执行(编译器可能不执行 RVO)。
例如,以下是否禁止使用复制省略(由于移动):
// notice that I fixed the OP's example by adding <double>
std::vector<double> foo(int bar){
std::vector<double> quux(bar, 0);
return std::move(quux);
}
是的,它确实是因为您没有返回局部变量的名称。这个
std::vector<double> foo(int bar){
std::vector<double> quux(bar,0);
return quux;
}
允许 RVO。有人可能会担心,如果不执行 RVO,那么移动比应对更好(这将解释std::move
上面的用法)。别担心。所有主要编译器都将在此处执行 RVO(至少在发布版本中)。即使编译器不执行 RVO 但满足 RVO 的条件,它也会尝试执行移动而不是复制。综上所述,使用std::move
上面肯定会有所动作。不使用它可能既不会复制也不会移动任何东西,并且在最坏(不太可能)的情况下会移动。
(更新:正如 haohaolee 指出的(见评论),以下段落是不正确的。但是,我把它们留在这里是因为它们提出了一个可能适用于没有构造函数的类的想法std::initializer_list
(参见参考资料底部)。对于std::vector
,haohaolee 找到了解决方法。)
在这个例子中,你可以通过返回一个可以创建返回类型的braced-init-list来强制RVO(严格来说这不再是RVO,但为了简单起见让我们继续这样调用) :
std::vector<double> foo(int bar){
return {bar, 0}; // <-- This doesn't work. Next line shows a workaround:
// return {bar, 0.0, std::vector<double>::allocator_type{}};
}
请参阅这篇文章和R. Martinho Fernandes的精彩回答。
小心点!如果返回类型是std::vector<int>
上面的最后一个代码,则会有与原始代码不同的行为。(这是另一个故事。)