返回值优化(RVO)是一种涉及复制省略的优化技术,它消除了在某些情况下为保存函数返回值而创建的临时对象.我一般都了解RVO的好处,但我有几个问题.
该标准在本工作草案的第12段第32段(强调我的)中说明了以下内容.
当满足某些条件时,允许实现省略类对象的复制/移动构造,即使该对象的复制/移动构造函数和/或析构函数具有副作用.在这种情况下,实现将省略的复制/移动操作的源和目标视为仅仅两种不同的引用同一对象的方式,并且该对象的销毁发生在两个对象的后期时间.没有优化就被破坏了.
然后,当实现可以执行此优化时,它会列出许多条件.
关于这种潜在的优化,我有几个问题:
我习惯于约束优化,以至于它们无法改变可观察的行为.此限制似乎不适用于RVO.我是否需要担心标准中提到的副作用?是否存在可能导致问题的角落案例?
作为程序员,我需要做什么(或不做)才能执行此优化?例如,以下是否禁止使用复制省略(由于move):
std::vector<double> foo(int bar){
std::vector<double> quux(bar,0);
return std::move(quux);
}
Run Code Online (Sandbox Code Playgroud)
我将此作为一个新问题发布,因为我提到的具体问题在其他相关问题中没有直接回答.
在C++ 03中,不可能通过值返回具有私有非定义复制构造函数的类的对象:
struct A { A(int x) { ... } private: A(A const&); };
A f() {
return A(10); // error!
return 10; // error too!
}
Run Code Online (Sandbox Code Playgroud)
我想知道,这个限制是否在C++ 11中解除了,是否可以编写具有类类型返回类型的函数,而没有用于复制或移动的构造函数?我记得允许函数的调用者使用新返回的对象可能很有用,但是他们无法复制值并将其存储在某处.
"如果从函数返回一个值(不是引用),那么将它绑定到调用函数中的const引用,它的生命周期将扩展到调用函数的范围."
所以:案例A.
const BoundingBox Player::GetBoundingBox(void)
{
return BoundingBox( &GetBoundingSphere() );
}
Run Code Online (Sandbox Code Playgroud)
const BoundingBox从函数返回类型的值GetBoundingBox()
变体I :(将它绑定到const引用)
const BoundingBox& l_Bbox = l_pPlayer->GetBoundingBox();
Run Code Online (Sandbox Code Playgroud)
变体II :(将它绑定到const副本)
const BoundingBox l_Bbox = l_pPlayer->GetBoundingBox();
Run Code Online (Sandbox Code Playgroud)
两者都工作正常,我没有看到l_Bbox对象超出范围.(虽然,我在变体1中理解,复制构造函数未被调用,因此稍微好于变体II).
另外,为了比较,我做了以下更改.
案例B
BoundingBox Player::GetBoundingBox(void)
{
return BoundingBox( &GetBoundingSphere() );
}
Run Code Online (Sandbox Code Playgroud)
与变体:我
BoundingBox& l_Bbox = l_pPlayer->GetBoundingBox();
Run Code Online (Sandbox Code Playgroud)
和II:
BoundingBox l_Bbox = l_pPlayer->GetBoundingBox();
Run Code Online (Sandbox Code Playgroud)
该对象l_Bbox仍然没有超出范围.如何"将它绑定到调用函数中的const引用,它的生命周期将扩展到调用函数的范围",真正将对象的生命周期延长到调用函数的范围?
我在这里错过了一些小事吗?
这里有很多关于何时可以进行RVO的讨论,但关于什么时候实际完成的情况并不多.如上所述,根据标准,无法保证RVO,但有没有办法保证RVO优化成功或相应的代码无法编译?
到目前为止,当RVO失败时,我部分成功地使代码发出链接错误.为此,我声明了复制构造函数而没有定义它们.显然,在我需要实现一个或两个拷贝构造函数的非罕见情况下,这既不强大也不可行,即x(x&&)和x(x const&).
这让我想到了第二个问题:为什么选择编译器编写器来在用户定义的复制构造函数到位时启用RVO,而不是仅在存在默认复制构造函数时?
第三个问题:是否有其他方法可以为普通数据结构启用RVO?
最后一个问题(承诺):您是否知道任何编译器使我的测试代码表现得与我用gcc和clang观察到的不同?
下面是gcc 4.6,gcc 4.8和clang 3.3的一些示例代码,用于显示问题.该行为不依赖于常规优化或调试设置.当然选项--no-elide-constructors会按照它说的做,即关闭RVO.
#include <iostream>
using namespace std;
struct x
{
x () { cout << "original x address" << this << endl; }
};
x make_x ()
{
return x();
}
struct y
{
y () { cout << "original y address" << this << endl; }
// Any of the next two constructors will enable RVO even if only …Run Code Online (Sandbox Code Playgroud) 在下面的代码中,我不允许声明一个显式的 ctor,因为编译器说我在复制初始化上下文中使用它(clang 3.3和gcc 4.8).我试图通过使ctor非显式,然后将复制构造函数声明为已删除来证明编译器是错误的.
编译器是错误还是有其他解释?
#include <iostream>
template <typename T>
struct xyz
{
constexpr xyz (xyz const &) = delete;
constexpr xyz (xyz &&) = delete;
xyz & operator = (xyz const &) = delete;
xyz & operator = (xyz &&) = delete;
T i;
/*explicit*/ constexpr xyz (T i): i(i) { }
};
template <typename T>
xyz<T> make_xyz (T && i)
{
return {std::forward<T>(i)};
}
int main ()
{
//auto && x = make_xyz(7);
auto && …Run Code Online (Sandbox Code Playgroud) 例:
struct s { int a; };
s func() { return {42}; }
int main() {
s new_obj = func(); // line 6
(void) new_obj;
return 0;
}
Run Code Online (Sandbox Code Playgroud)
这有效.现在,如果我们假设我们的编译器没有RVO会发生什么?
func返回一个struct的结构s,因此{42}必须转换为s,然后返回并最终复制到new_obj第6行.func 返回初始化列表,因此无法进行深层复制.语言是什么意思?你能举一个证明吗?
注意:我知道这在这个例子中看起来没用,但是对于返回非常大的常量std::arrays,我不想依赖RVO.
要理解这个问题,请先阅读此答案.
我检查了不同的历史make_tuple实现(包括2012年的clang版本).在C++ 17之前我会期望它们,return {list of values ... }但它们都会在返回之前构造元组.它们都是非常简化的当前cppreference示例:
template <class... Types>
auto make_tuple(Types&&... args)
{
return std::tuple<special_decay_t<Types>...>(std::forward<Types>(args)...);
}
Run Code Online (Sandbox Code Playgroud)
它没错,但返回大括号初始化的重点是直接构造返回的对象.在C++ 17之前,没有保证的复制省略甚至在概念上消除了临时性.但即使使用C++ 17,我也不一定会期望花括号在这个例子中消失.
为什么在任何C++ 11/14实现中都没有花括号?换句话说,为什么不呢
template <class... Types>
std::tuple<special_decay_t<Types>...> make_tuple(Types&&... args)
{
return {std::forward<Types>(args)...};
}
Run Code Online (Sandbox Code Playgroud)