在分配给不同类型时,返回值优化是否有效?

时间:2011-04-24 14:48:25

标签: c++ return-value-optimization

考虑以下两个类:

class Base  
{  
  Base(const Base& other) {...} // relatively expensive operations here...
  Base(int i)             {...} // ...here,
  virtual ~Base()         {...} // ...and here
  ...
};

class Derived : public Base
{
  ...
  Derived(const Base& other)   :Base(other) {...} // some typechecking in here 
  virtual ~Derived() {}
  ...
};

这意味着 Base 可以通过 Derived 的第二个构造函数“upcast”。 现在考虑以下代码:

Base getBase()  
{
   int id = ...
   return Base(id);
}
...
int main()
{
   Base    b = getBase();   // CASE 1
   Derived d1(b);           // "upcast"

   Derived d2 = getBase();  // CASE 2
   ...
}

我正在使用VS2008打开优化(/ Ox / Ob2 / Oi / Ot)。我在console-output上检查了对构造函数和析构函数的调用:

案例1 中,返回值优化有效。有两个电话:

  1. 基(int)的
  2. 〜基地()
  3. 但是,当 main 中需要派生对象时,这里没有什么可赢的。 “upcast”需要另一个构造函数/析构函数对。

    案例2 中,返回值优化不起作用。这里创建并销毁了两个对象:

    1. Base(int) //创建临时
    2. ~Base() //销毁临时
    3. Base(const Base&) // via Derived(const Base&)
    4. ~Base() //来自~Derived()
    5. 现在在我看来,我有三个相互矛盾的要求:

      1. 我想避免创建临时对象的开销(因为对象创建和销毁在类 Base 中相当昂贵)
      2. main 中,我需要 Derived -object而不是 Base -object才能使用。
      3. 显然,这里没有免费的午餐。但我可能错过了一些东西。所以我的问题是:有没有办法结合这些要求?或者有没有人有类似的经历?

        旁注:我知道“upcast”Derived(const Base& other)在运行时可能会失败(这已经得到了解决)。由于代码在语法层面上没问题,我猜这不是编译器避免RVO的原因。

2 个答案:

答案 0 :(得分:2)

这很糟糕。

Derived(const Base& other)   :Base(other) {...}

other的静态类型可以属于派生类型。在那种情况下,它将被切片。最重要的是,该基类将被复制。

RVO是绕过复制构造函数并就地初始化返回的对象。如果需要派生类型的对象,则必须先构造它。 RVO无法为您构建它。

而不是Derived(const Base& other)您可能需要考虑不同的方法。怎么样:

class Base  
{
  ...
  // extract expensive parts of another instance
  virtual void initialise(Base& b);
  ...
};

class Derived : public Base
{
  ...
  Derived(); // cheap constructor
  void initialise(Base& b) { /* implementation goes here */  }
  ...
};

initialise(Base& b)方法会从参数中提取昂贵的部分。它可能具有破坏性。 Base将提供公共(或可能受保护)的接口来进行实际提取。

答案 1 :(得分:0)

如何将构造函数添加到Derived

Derived(Base (*f)(void)) : Base(f()) { ... }

然后Derived d3 = getBase;可能会为您提供所需的优化。可能不太实际,因为我必须在f中指定Derived的空参数列表,这是非常有限的。但是使它成为一个模板构造函数,你可以使用一个用户编写的函子,一个boost:bind或一个C ++ 0x lambda的结果。

如果失败,请将id = ... getBase部分提取到函数getId中,并为Derived提供一个int的构造函数,该id传递Base转到其BaseParameters子对象。毫无疑问,真正的代码比这更复杂,并且可能导致拖拽有关该地方的许多参数。也许是一个轻量级Base类,您可以使用它代替Base,直到您实际需要完成缓慢的工作,然后将 转换为Derived ,{{1}}或其他相关课程。