Search code examples
c++return-value-optimization

Does return value optimization work, when assigning to a different type?


Consider the following two classes:

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() {}
  ...
};

This means Base can be "upcast" by means of the second constructor of Derived. Now consider the following code:

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

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

I am using VS2008 with optimizations turned on (/Ox /Ob2 /Oi /Ot). I checked calls to constructors and destructors on the console-output:

In Case 1 return value optimization works. There are two calls to:

  1. Base(int)
  2. ~Base()

However, there is nothing to be won here, when a Derived-object is needed in main. The "upcast" requires another constructor/destructor pair.

In Case 2 return value optimization does not work. Two objects are created and destroyed here:

  1. Base(int) //Create temporary
  2. ~Base() //Destroy temporary
  3. Base(const Base&) //via Derived(const Base&)
  4. ~Base() //via ~Derived()

Now it seems to me, that I have three conflicting requirements:

  1. I'd like to avoid the overhead of creating the temporary object (because object creation and destruction is rather expensive in class Base)
  2. In main, I need a Derived-object instead of a Base-object to work with.

Obviously, there's no free lunch here. But I might have missed something. So my question is: Is there a way to combine these requirements? Or has anyone had similar experiences?

Sidenote: I am aware of the fact, that the "upcast" Derived(const Base& other) might fail during runtime (this has been taken care of). Since the code is ok on syntactic level, I'd guess this not the reason for the compiler to avoid RVO.


Solution

  • This is bad.

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

    The static type of other can belong to the derived type. In that case it will get sliced. On top of that base class would be copied anyway.

    RVO is about bypassing copy constructor and initialising the returned object in-place. If you need an object of the derived type you would have to to construct it first. RVO can't construct it for you.

    Instead of Derived(const Base& other) you may want to consider different approach. How about this:

    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) method would extract expensive parts from the argument. It can be destructive. Base will provide public (or maybe protected) interface to do the actual extraction.