# CSE332S Object-Oriented Programming in C++ (Lecture 15) ## Move semantics introduction and motivation 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, if 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` ## Move constructor and assignment operator examples, more details on inheritance ### R-values, L-values, and Reference 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) ### Move Constructors ```cpp // 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; } ``` 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) ### Move Assignment Operator 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 ```cpp 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::operator=` 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