Copy Constructor Elision

What are copy elision and return value optimization?

Introduction

For a technical overview - skip to this answer.

For common cases where copy elision occurs - skip to this answer.

Copy elision is an optimization implemented by most compilers to prevent extra (potentially expensive) copies in certain situations. It makes returning by value or pass-by-value feasible in practice (restrictions apply).

It's the only form of optimization that elides (ha!) the as-if rule - copy elision can be applied even if copying/moving the object has side-effects.

The following example taken from Wikipedia:

struct C {
C() {}
C(const C&) { std::cout << "A copy was made.\n"; }
};

C f() {
return C();
}

int main() {
std::cout << "Hello World!\n";
C obj = f();
}

Depending on the compiler & settings, the following outputs are all valid:

Hello World!

A copy was made.

A copy was made.



Hello World!

A copy was made.



Hello World!

This also means fewer objects can be created, so you also can't rely on a specific number of destructors being called. You shouldn't have critical logic inside copy/move-constructors or destructors, as you can't rely on them being called.

If a call to a copy or move constructor is elided, that constructor must still exist and must be accessible. This ensures that copy elision does not allow copying objects which are not normally copyable, e.g. because they have a private or deleted copy/move constructor.

C++17: As of C++17, Copy Elision is guaranteed when an object is returned directly:

struct C {
C() {}
C(const C&) { std::cout << "A copy was made.\n"; }
};

C f() {
return C(); //Definitely performs copy elision
}
C g() {
C c;
return c; //Maybe performs copy elision
}

int main() {
std::cout << "Hello World!\n";
C obj = f(); //Copy constructor isn't called
}

copy elision in c++03

Yes, copy ellision is permitted in C++03 and C++98. That's the paragraph for C++98 and C++03:

Non-mandatory elision of copy operations

Under the following circumstances, the compilers are permitted, but
not required to omit the copy construction of
class objects even if the copy constructor and the
destructor have observable side-effects. The objects are constructed
directly into the storage where they would otherwise be copied
to. This is an optimization: even when it takes place and the
copy constructor is not called, it still must be
present and accessible (as if no optimization happened at all),
otherwise the program is ill-formed:

  • In a return statement, when the operand is the name of a non-volatile object with automatic storage duration, which isn't a
    function parameter or a catch clause parameter, and which is of the
    same class type (ignoring cv-qualification) as the function return
    type. This variant of copy elision is known as NRVO, "named return
    value optimization".

  • In the initialization of an object, when the source object is a nameless temporary and is of the same class type (ignoring
    cv-qualification) as the target object. When the nameless temporary is
    the operand of a return statement, this variant of copy elision is
    known as RVO, "return value optimization".

When copy elision occurs, the implementation treats the source and target of the omitted copy operation as simply two different ways of referring to the same object, and the destruction of that object occurs at the later of the times when the two objects would have been destroyed without the optimization

cppreference

I removed everything that's only valid since C++11.

The only differences between C++98, C++03 and C++11 regarding ellision are move operations and exception handling.

Guaranteed copy elision and deleted copy/move constructor when throwing an exception

Your understanding of the standard is correct, in so far as your analysis of that paragraph actually applies.

But it doesn't apply, because no constructor is ever considered for copy-initialization.

Guaranteed elision is something of a misnomer; it's a useful explanation of the concept, but it doesn't reflect exactly how it works as far as the standard is concerned. Guaranteed elision works by rewriting the meaning of prvalues so there never is a copy/move to be elided in the first place.

A a = A{}; is copy-initialization, but it doesn't even hypothetically call a copy/move constructor. The variable a is initialized by the prvalue's initializer:

The result of a prvalue is the value that the expression stores into its context. A prvalue whose result is the value V is sometimes said to have or name the value V. The result object of a prvalue is the object initialized by the prvalue

a is the "result object" initialized by the prvalue.

The same goes here. A{} is a prvalue. The exception object is the "result object" to be initialized by the prvalue. There's no temporary object, no copy/move constructors that ever get considered for usage.

Copy constructor elision?

Yes this is copy elision through Named Return Value Optimization.

The C++ standard allows an implementation to omit a copy operation resulting from a return statement, even if the copy constructor has side effects.

Reference:

C++03 Standard:
12.8 Copying class objects:

# 15

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. In such cases, the implementation treats the source and target of the omitted copy operation as simply two different ways of referring to the same object, and the destruction of that object occurs at the later of the times when the two objects would have been destroyed without the optimization.111) This elision of copy operations is permitted in the
following circumstances (which may be combined to eliminate multiple copies):

— in a return statement in a function with a class return type, when the expression is the name of a non-volatile automatic object with the same cv-unqualified type as the function return type, the copy operation can be omitted by constructing the automatic object directly into the function’s return value

— when a temporary class object that has not been bound to a reference (12.2) would be copied to a class object with the same cv-unqualified type, the copy operation can be omitted by constructing the temporary object directly into the target of the omitted copy

Move constructor vs Copy elision

There are plenty of cases where the move constructor will still get called and copy elision is not being used:

// inserting existing objects into a container
MyObject myobject;
std::vector<MyObject> myvector;
myvector.push_back(std::move(myobject));

// inserting temporary objects into a container
myvector.push_back(MyObject());

// swapping
MyObject other;
std::swap(myobject, other);

// calling functions with existing objects
void foo(MyObject x);

foo(std::move(myobject));

... and many more.

The only instance where there is mandatory copy elision (since C++17) is when constructing values from the result of a function call or a constructor. In such cases, the compiler isn't even allowed to use the move constructor. For example:

MyObject bar() {
return MyObject();
}

void example() {
MyObject x = bar(); // copy elision here
MyObject y = MyObject(); // also here
}

In general, the purpose of copy elision is not to eliminate move construction alltogether, but to avoid unnecessary constructions when initializing variables from prvalues.


See cppreference on Copy Elision.

How to enforce copy elision, why it won't work with deleted copy constructor?

Until C++17 copy elision is an optimization the compiler is not required to do, so classes must be copyable since the compiler might want to copy (even if it actually does not). In C++17 copy elision will be guaranteed in many cases and then classes won't need copy ctors.

See also:

http://en.cppreference.com/w/cpp/language/copy_elision

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0135r0.html

https://herbsutter.com/2016/06/30/trip-report-summer-iso-c-standards-meeting-oulu/
(the bit about "Guaranteed copy elision")

You could perhaps use the old trick of declaring the copy constructor in your class but not actually implement it? That should please the compiler as long as it does not actually invoke the copy ctor. I didn't test that, but I believe it should work for your case until C++17 arrives.

Copy constructor elision?

It is an optimization done by the compiler. According to the language specification, the compiler is allowed to omit the call to the copy-constructor whenever it can.

An accessible copy-constructor is needed for semantic check only, even though it is not actually called. Semantic check is done much before the optimization.

However, if you compile it with -fno-elide-constructors option with GCC, then the copy-elision will not be performed, and the copy-constructor will be called. The GCC doc says,

-fno-elide-constructors

The C++ standard allows an implementation to omit creating a temporary which is only used to initialize another object of the same type. Specifying this option disables that optimization, and forces G++ to call the copy constructor in all cases.

With MSVC10, you can use /Od which according to the MSDN turns off all optimizations in the program.

Note : Wikipedia has an article on copy elision



Related Topics



Leave a reply



Submit