相关疑难解决方法(0)

返回值优化和副作用

返回值优化(RVO)是一种涉及复制省略的优化技术,它消除了在某些情况下为保存函数返回值而创建的临时对象.我一般都了解RVO的好处,但我有几个问题.

该标准在本工作草案的第12段第32段(强调我的)中说明了以下内容.

当满足某些条件时,允许实现省略类对象的复制/移动构造,即使该对象的复制/移动构造函数和/或析构函数具有副作用.在这种情况下,实现将省略的复制/移动操作的源和目标视为仅仅两种不同的引用同一对象的方式,并且该对象的销毁发生在两个对象的后期时间.没有优化就被破坏了.

然后,当实现可以执行此优化时,它会列出许多条件.


关于这种潜在的优化,我有几个问题:

  1. 我习惯于约束优化,以至于它们无法改变可观察的行为.此限制似乎不适用于RVO.我是否需要担心标准中提到的副作用?是否存在可能导致问题的角落案例?

  2. 作为程序员,我需要做什么(或不做)才能执行此优化?例如,以下是否禁止使用复制省略(由于move):

std::vector<double> foo(int bar){
    std::vector<double> quux(bar,0);
    return std::move(quux);
}
Run Code Online (Sandbox Code Playgroud)

编辑

我将此作为一个新问题发布,因为我提到的具体问题在其他相关问题中没有直接回答.

c++ compiler-optimization c++11

26
推荐指数
2
解决办法
1738
查看次数

我们可以通过函数的值返回具有已删除/私有复制/移动构造函数的对象吗?

在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中解除了,是否可以编写具有类类型返回类型的函数,而没有用于复制或移动的构造函数?我记得允许函数的调用者使用新返回的对象可能很有用,但是他们无法复制值并将其存储在某处.

c++ copy-constructor c++11

16
推荐指数
3
解决办法
2787
查看次数

当它被绑定到调用函数中的const引用时,它的返回值的生命周期如何扩展到调用函数的范围?

"如果从函数返回一个值(不是引用),那么将它绑定到调用函数中的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引用,它的生命周期将扩展到调用函数的范围",真正将对象的生命周期延长到调用函数的范围?

我在这里错过了一些小事吗?

c++ memory const pass-by-reference

9
推荐指数
2
解决办法
2587
查看次数

失败时RVO强制编译错误

这里有很多关于何时可以进行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)

c++ inline compiler-optimization copy-elision c++11

9
推荐指数
1
解决办法
636
查看次数

这不是复制初始化,不是吗?

在下面的代码中,我不允许声明一个显式的 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)

c++ explicit-constructor copy-initialization c++11

7
推荐指数
1
解决办法
477
查看次数

可以返回一个括起来的封闭初始化器导致C++中的副本吗?

例:

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会发生什么?

  1. func返回一个struct的结构s,因此{42}必须转换为s,然后返回并最终复制到new_obj第6行.
  2. func 返回初始化列表,因此无法进行深层复制.

语言是什么意思?你能举一个证明吗?

注意:我知道这在这个例子中看起来没用,但是对于返回非常大的常量std::arrays,我不想依赖RVO.

c++ initializer-list return-value-optimization c++11

7
推荐指数
1
解决办法
370
查看次数

为什么make_tuple的实现不会通过大括号初始化返回?

要理解这个问题,请先阅读此答案.

我检查了不同的历史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)

c++ c++11 stdtuple list-initialization c++17

1
推荐指数
1
解决办法
229
查看次数