70 likes | 189 Views
Copy Control (Part II). Review: copy control consists of 5 distinct operations A copy constructor initializes an object by duplicating the const l-value that was passed to it by reference
E N D
Copy Control (Part II) • Review: copy control consists of 5 distinct operations • A copy constructor initializes an object by duplicating the const l-value that was passed to it by reference • A copy-assignment operator (re)sets an object’s value by duplicating the const l-value passed to it by reference • A destructor manages the destruction of an object • A move constructor initializes an object by transferring the implementation from the r-value reference passed to it • A move-assignment operator (re)sets an object’s value by transferring the implementation from the r-value reference passed to it • Today we’ll focus on the last 2 operations and other features (introduced in C++11) like r-value references • I.e., features that support the new C++11 move semantics
Motivation for Move Semantics • Copy construction and copy-assignment may be expensive due to time/memory for copying • It would be more efficient to simply “take” the implementation from the passed object, it that’s ok • It’s ok if the passed object won’t be used afterward • E.g., if it was passed by value and so is a temporary object • E.g., if a special r-value reference says it’s ok to take from (as long as object remains in a state that’s safe to destruct) • Note that some objects require move semantics • I.e., types that don’t allow copy construction/assignment • E.g., unique_ptr, ifstream, thread, etc. • New for C++11: r-value references and move function • E.g., int i; int &&rvri = std::move(i);
Synthesized Move Operations • Compiler will only synthesize a move operation if • Class does not declare any copy control operations, and • Every non-static data member of the class can be moved • Members of built-in types can be moved • E.g., by std::move etc. • User-defined types that have synthesized/defined version of the specific move operation can be moved • L-values are always copied, r-values can be moved • If there is no move constructor, r-values only can be copied • Can ask for a move operation to be synthesized • I.e., by using = default • But if cannot move all members, synthesized as = delete
R-Values, L-Values, and References to Either • A variable is an l-value (has a location) • E.g., int i = 7; • Can take a regular (l-value) reference to it • E.g., int & lvri = i; • An expression is an r-value • E.g., i * 42 • Can only take an r-value reference to it (note syntax) • E.g., int && rvriexp = i * 42; • Can only get r-value reference to l-value via move • E.g., int && rvri = std::move(i); • Promises that i won’t be used for anything afterward • Also, must be safe to destroy i (could be stack/heap/global)
Note r-value reference Says it’s safe to take a’s implementation from it Promises only subsequent operation will be destruction Note constructor design A lot like shallow copy constructor’s implementation Except, zeroes out state of a No sharing, current object owns the implementation Object a is now safe to destroy (but is not safe to do anything else with afterward) // takes implementation from a IntArray::IntArray(IntArray &&a) : size_(a.size_), values_(a.values_) { // make a safe to destroy a.values_ = nullptr; a.size_ = 0; } Move Constructors
Move-Assignment Operators • No allocation, so no exceptions to worry about • Simply free existing implementation (delete values_) • Then copy over size and pointer values from a • Then zero out size and pointer in a • This leaves assignment complete, a safe to destroy • Implementation is transferred from a to current object Array & Array::operator=(Array &&a) { // Note r-value reference if (&a != this) { // still test for self-assignment delete [] values_; // safe to free first (if not self-assigning) size_ = a. size_; // take a’s size value values_ = a.values_; // take a’s pointer value a.size_ = 0; // zero out a’s size a.values_ = nullptr; // zero out a’s pointer (now safe to destroy) } return *this; }
Move Semantics and Inheritance • Base classes should declare/define move operations • If it makes sense to do so at all • Derived classes then can focus on moving their members • E.g., calling Base::operator= from Derived:: version • Containers further complicate these issues • Containers hold their elements by value • Risks slicing, other inheritance and copy control problems • So, put (smart) pointers, not objects, into containers • Access is polymorphic if destructors, other methods virtual • Smart pointers may help reduce need for copy control operations, or at least simplify cases where needed