views:

144

answers:

7

I ran across this article on copy ellision in C++ and I've seen comments about it in the boost library. This is appealing, as I prefer my functions to look like

verylargereturntype DoSomething(...)

rather than

void DoSomething(..., verylargereturntype& retval)

So, I have two questions about this

  1. Google has virtually no documentation on this at all, how real is this?
  2. How can I check that this optimization is actually occuring? I assume it involves looking at the assembly, but lets just say that isn't my strong suit. If anyone can give a very basic example as to what successful ellision looks like, that would be very useful

I won't be using copy ellision just to prettify things, but if I can be guaranteed that it works, it sounds pretty useful.

+3  A: 

The only way to know for sure is to look at the assembly, but you're asking the wrong question. You don't need to know if the compiler is eliding the copy unless it matters to the program timing. A profiler should easily tell you if you're spending too much time in the copy constructor.

The poor man's way to figure it out is to put a static counter in the copy constructor and try both forms of your function. If the counts are the same, you've successfully avoided the copy.

Mark Ransom
this thing is really big and called pretty often
Steve
"Really big" and "pretty often" might be orders of magnitude larger than you think they are. It amazes me what a modern computer can do in a fraction of a second.
Mark Ransom
+2  A: 

Google "Named Return Value Optimization" and "Return Value Optimization" instead. Modern compilers will in fact not perform the copy in many cases.

You can check if it's occurring by returning a type with side effects -- such as printing a message. Wikipedia has some good examples of where program output changes when RVO and/or NRVO is in effect.

Billy ONeal
+5  A: 

From your cited article:

Although copy elision is never required by the standard, recent versions of every compiler I’ve tested do perform these optimizations today. But even if you don’t feel comfortable returning heavyweight objects by value, copy elision should still change the way you write code.

It is better known as Return Value Optimization.

msw
+9  A: 

I think this is a very commonly applied optimization because:

  1. it's not difficult for the compiler to do
  2. it can be a huge gain
  3. it's an area of C++ that was a commonly critiqued before the optimization became common

If you're just curious, put a debug printf() in your copy constructor:

class foo {
public:
    foo(): x(0) {};

    foo(int x_) : x( x_) {};

    foo( foo const& other) : x( other.x) {
        printf( "copied a foo\n");
    };

    static foo foobar() {
        foo tmp( 2);

        return tmp;
    }


private:
    int x;
};



int main()
{
    foo myFoo;

    myFoo = foo::foobar();

    return 0;
}

Prints out "copied a foo" when I run an unoptimmized build, but nothing when I build optimized.

Michael Burr
On some compilers RVO can be invoked even with minimal or no optimization settings.
fbrereto
I'm kinda terrified that the compiler optimized out an intended side effect... Seems like it should only optimize your copy constructor out if it's the default copy constructor or something like that.
sblom
The standard explicitly allows this optimization, so the long and short of it is you shouldn't depend on side effects of your copy constructor, beyond the obvious. This is a pretty important optimization technique, and restricting it to objects with trivial copy constructors would be extremely limiting.
Dennis Zickefoose
@sblom: As Dennis says, the standard explicitly says (12.8/12 "Copying class objects"): "When certain criteria are met, an implementation is allowed to omit the copy construction of a class object, even if the copy constructor and/or destructor for the object have side effects"
Michael Burr
@sblom: there is only one copy constructor and it's only optimized out if the object it's copying from is unnamed. If you want the side effects, simply give the intermediate object a name.
Potatoswatter
@Potatocom: Even if the returned value is named, the copy can still be optimized out. It is harder, but compilers are smart.
Dennis Zickefoose
In fact, the example I posted and tested uses a named return value (a simple one, yes, but named nonetheless). Named return value optimization is known as NRVO, and might be a bit less common than plain RVO, but I think they're pretty much handled the same by modern compilers.
Michael Burr
@Michael: it depends on compiler and optimization settings. Visual is known not to implement RVO in Debug builds while gcc does URVO I think. Now I am not sure it's really worth digging into the specs of the compiler to precisely know which optimization is applied at which level...
Matthieu M.
+1  A: 

To answer question 2, you could write a demo program where you write a class DemoReturnType; which has instrumented constructors and destructors which just write to cout when they are called. This should give you enough information about what your compiler is capable of.

quamrana
+2  A: 

Example of what it looks like:

#include <iostream>

struct Foo {
    int a;
    Foo(int a) : a(a) {}
    Foo(const Foo &rhs) : a(rhs.a) { std::cout << "copying\n"; }
};

int main() {
    Foo f = Foo(1);
}

If you see no output, then copy elision has taken place. That's elision of a copy from an initializer. The other legal case of copy elision is a return value, and is tested by:

Foo getFoo() {
    return Foo(1);
}

int main() {
    Foo f = getFoo();
}

or more excitingly for a named return value:

Foo getFoo() {
    Foo f(1);
    return f;
}

int main() {
    Foo f = getFoo();
}

g++ performs all those elisions for me with no optimisation flags, but you can't really know whether more complex code will outwit the compiler.

Note that copy elision doesn't help with assignment, so the following will always result in a call to operator= if that operator prints anything:

Foo f(1);
f = getFoo();

Returning by value therefore can still result in "a copy", even if copy constructor elision is performed. So for chunking great classes it's still a performance consideration at the design stage. You don't want to write your code such that fixing it later will be a big deal if it turns out your app spends a significant proportion of its time in copying that could have been avoided.

Steve Jessop
+1  A: 

Rvalue references solve this problem in C++0x. Whether or not you can obtain an rvalue-enabled compiler is another question - last time I checked only Visual Studio 2010 supports it.

DeadMG
GCC supports it too. See http://www.aristeia.com/C++0x/C++0xFeatureAvailability.htm and click the "Language" tab at the bottom.
Nate
Rvalue references compliment [N]RVO. When possible, the compiler will still chose to construct the object in place and avoid the move/copy outright.
Dennis Zickefoose