Skip to content

Pass By Value Copy Constructor And Assignment

Omits copy- and move-(since C++11)constructors, resulting in zero-copy pass-by-value semantics.

[edit]Explanation

Under the following circumstances, the compilers are required to omit the copy- and move- construction of class objects even if the copy/move constructor and the destructor have observable side-effects. They need not be present or accessible, as the language rules ensure that no copy/move operation takes place, even conceptually:

  • In initialization, if the initializer expression is a prvalue and the cv-unqualified version of the source type is the same class as the class of the destination, the initializer expression is used to initialize the destination object:
  • In a function call, if the operand of a return statement is a prvalue and the return type of the function is the same as the type of that prvalue.

Note: the rule above does not specify an optimization: C++17 core language specification of prvalues and temporaries is fundamentally different from that of the earlier C++ revisions: there is no longer a temporary to copy/move from. Another way to describe C++17 mechanics is "unmaterialized value passing": prvalues are returned and used without ever materializing a temporary.

T x = T(T(T()));// only one call to default constructor of T, to initialize x
T f(){return T{};} T x = f();// only one call to default constructor of T, to initialize x T* p = new T(f());// only one call to default constructor of T, to initialize *p
(since C++17)

Under the following circumstances, the compilers are permitted, but not required to omit the copy- and move-(since C++11)construction of class objects even if the copy/move(since C++11) constructor and the destructor have observable side-effects. This is an optimization: even when it takes place and the copy-/move-constructor is not called, it still must be present and accessible (as if no optimization happened at all), otherwise the program is ill-formed.

  • If a function returns a class type by value, and the return statement's expression 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 has the same type (ignoring top-level cv-qualification) as the return type of the function, then copy/move(since C++11) is omitted. When that local object is constructed, it is constructed directly in the storage where the function's return value would otherwise be moved or copied to. This variant of copy elision is known as NRVO, "named return value optimization".
  • When a nameless temporary, not bound to any references, would be copied or moved(since C++11) into an object of the same type (ignoring top-level cv-qualification), the copy/move(since C++11) is omitted. When that temporary is constructed, it is constructed directly in the storage where it would otherwise be copied or moved(since C++11) to. When the nameless temporary is the argument of a return statement, this variant of copy elision is known as RVO, "return value optimization".
(until C++17)

This optimization is mandatory; see above.

(since C++17)
  • In a throw-expression, if 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 whose scope does not extend past the innermost try-block (if there is a try-block), then copy/move is omitted. When that local object is constructed, it is constructed directly in the storage where the exception object would otherwise be moved or copied to.
  • When handling an exception, if the argument of the catch clause is of the same type (ignoring top-level cv-qualification) as the exception object thrown, the copy is omitted and the body of the catch clause accesses the exception object directly, as if caught by reference. This is disabled if such copy elision would change the observable behavior of the program for any reason other than skipping the copy constructor and the destructor of the catch clause's argument (for example, if the catch clause argument is modified, and the exception object is rethrown with throw;).
(since C++11)

When copy elision occurs, the implementation treats the source and target of the omitted copy/move(since C++11) 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 (except that, if the parameter of the selected constructor is an rvalue reference to object type, the destruction occurs when the target would have been destroyed)(since C++17).

Multiple copy elisions may be chained to eliminate multiple copies.

struct A {void*p;constexpr A(): p(this){}};   constexpr A g(){ A a;return a;}   constexpr A a;// a.p points to aconstexpr A b = g();// b.p points to b (NRVO guaranteed)   void g(){ A c = g();// c.p may point to c or to an ephemeral temporary}
(since C++14)

[edit]Notes

Copy elision is the only allowed form of optimization(until C++14)one of the two allowed forms of optimization, alongside allocation elision and extension,(since C++14) that can change the observable side-effects. Because some compilers do not perform copy elision in every situation where it is allowed (e.g., in debug mode), programs that rely on the side-effects of copy/move constructors and destructors are not portable.

In a return statement or a throw-expression, if the compiler cannot perform copy elision but the conditions for copy elision are met or would be met, except that the source is a function parameter, the compiler will attempt to use the move constructor even if the object is designated by an lvalue; see return statement for details.

(since C++11)

[edit]Example

Run this code

Possible output:

#include <iostream>#include <vector>   struct Noisy { Noisy(){std::cout<<"constructed\n";} Noisy(const Noisy&){std::cout<<"copy-constructed\n";} Noisy(Noisy&&){std::cout<<"move-constructed\n";} ~Noisy(){std::cout<<"destructed\n";}};   std::vector<Noisy> f(){std::vector<Noisy> v =std::vector<Noisy>(3);// copy elision when initializing v// from a temporary (until C++17)// from a prvalue (since C++17)return v;// NRVO from v to the result object (not guaranteed in C++17)}// if optimization is disabled, the move constructor is called   void g(std::vector<Noisy> arg){std::cout<<"arg.size() = "<< arg.size()<<'\n';}   int main(){std::vector<Noisy> v = f();// copy elision in initialization of v// from the temporary returned by f() (until C++17)// from the prvalue f() (since C++17) g(f());// copy elision in initialization of the parameter of g()// from the temporary returned by f() (until C++17)// from the prvalue f() (since C++17)}
constructed constructed constructed constructed constructed constructed arg.size() = 3 destructed destructed destructed destructed destructed destructed

[edit]Defect reports

The following behavior-changing defect reports were applied retroactively to previously published C++ standards.

DR Applied to Behavior as published Correct behavior
CWG 2022 C++14 copy elision was optional in constant expressions copy elision mandatory

[edit]See also

In the C++programming language, the assignment operator, , is the operator used for assignment. Like most other operators in C++, it can be overloaded.

The copy assignment operator, often just called the "assignment operator", is a special case of assignment operator where the source (right-hand side) and destination (left-hand side) are of the same class type. It is one of the special member functions, which means that a default version of it is generated automatically by the compiler if the programmer does not declare one. The default version performs a memberwise copy, where each member is copied by its own copy assignment operator (which may also be programmer-declared or compiler-generated).

The copy assignment operator differs from the copy constructor in that it must clean up the data members of the assignment's target (and correctly handle self-assignment) whereas the copy constructor assigns values to uninitialized data members.[1] For example:

My_Arrayfirst;// initialization by default constructorMy_Arraysecond(first);// initialization by copy constructorMy_Arraythird=first;// Also initialization by copy constructorsecond=third;// assignment by copy assignment operator

Return value of overloaded assignment operator[edit]

The language permits an overloaded assignment operator to have an arbitrary return type (including ). However, the operator is usually defined to return a reference to the assignee. This is consistent with the behavior of assignment operator for built-in types (returning the assigned value) and allows for using the operator invocation as an expression, for instance in control statements or in chained assignment. Also, the C++ Standard Library requires this behavior for some user-supplied types.[2]

Overloading copy assignment operator[edit]

When deep copies of objects have to be made, exception safety should be taken into consideration. One way to achieve this when resource deallocation never fails is:

  1. Acquire new resources
  2. Release old resources
  3. Assign the new resources' handles to the object
classMy_Array{int*array;intcount;public:My_Array&operator=(constMy_Array&other){if(this!=&other)// protect against invalid self-assignment{// 1: allocate new memory and copy the elementsint*new_array=newint[other.count];std::copy(other.array,other.array+other.count,new_array);// 2: deallocate old memorydelete[]array;// 3: assign the new memory to the objectarray=new_array;count=other.count;}// by convention, always return *thisreturn*this;}// ...};

However, if a no-fail (no-throw) swap function is available for all the member subobjects and the class provides a copy constructor and destructor (which it should do according to the rule of three), the most straightforward way to implement copy assignment is as follows:[3]

public:voidswap(My_Array&other)// the swap member function (should never fail!){// swap all the members (and base subobject, if applicable) with otherusingstd::swap;// because of ADL the compiler will use // custom swap for members if it exists// falling back to std::swapswap(array,other.array);swap(count,other.count);}My_Array&operator=(My_Arrayother)// note: argument passed by value!{// swap this with otherswap(other);// by convention, always return *thisreturn*this;// other is destroyed, releasing the memory}

Assignment between different classes[edit]

C++ supports assignment between different classes, both via implicit copy constructor and assignment operator, if the destination instance class is the ancestor of the source instance class:

classAncestor{public:inta;};classDescendant:publicAncestor{public:intb;};intmain(){Descendantd;Ancestora(d);Ancestorb(d);a=d;return0;}

Copying from ancestor to descendant objects, which could leave descendant's fields uninitialized, is not permitted.

See also[edit]

References[edit]

External links[edit]

  1. ^Stroustrup, Bjarne (2000). The C++ Programming Language (3 ed.). Addison-Wesley. p. 244. ISBN 978-0-201-70073-2. 
  2. ^Working Draft, Standard for Programming Language C++, Section 17.6.3.1, Table 23; http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3337.pdf
  3. ^Sutter, H.; Alexandrescu, A. (October 2004), C++ Coding Standards, Addison-Wesley, ISBN 0-321-11358-6