Skip to Content
CSE332SObject-Oriented Programming Lab (Lecture 15)

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

// 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
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
Last updated on