Introduction to move semantics (part 1)

This lesson contains approximately 20 minutes of video content.

Introduction to move semantics

Remember, the two copy operations are independent: declaring the copy constructor doesn't prevent the compiler from generating the copy assignment operator for you, or vice versa. Meanwhile, the move constructor and move assignment operator are not independent: declaring either will prevent the compiler from generating the other.

If you define copy semantics for your type, move operations won't be provided by the compiler. In this case, if they were defined automatically, they probably wouldn't be defined how you'd like them to be. Also worth noting: if you declare a move operation, the compiler will not provide the copy operations, so you'll need to define them yourself. This makes sense again: since you defined move semantics, the compiler-generated copy semantics probably wouldn't provide the copy policy you want.

Move operators are automatically generated for your class, only if:

  • No copy operations are declared
  • No move operations are declared
  • No destructor is declared in that class

Keep in mind, what the compiler provides might not be what you want! Always follow the "rule of five": if you declare one of the five functions, declare the other four too. As a reminder, the "rule of five" functions are:

  • Destructor
  • Copy constructor
  • Copy assignment operator
  • Move constructor
  • Move assignment operator

Meyers, S. (2014). Effective Modern C++: 42 Specific Ways to Improve Your Use of C++11 and C++14. O'Reilly Media, Inc.


Let's consider object ownership

Activity: Move semantics for cll

Graded Playground Autograder

Activity Prompt:

In this programming problem, you will implement move semantics for CircularLinkedList<T>. We only require you to define a move constructor and move assignment operator, though you might want to define additional behaviors to test your implementation. After the ownership transfer, the object on the right-hand side of the = sign should have head_ == tail_ == nullptr and node_order_ == Order::kASC.

Member functions you are to implement
CircularLinkedList(CircularLinkedList<T>&& source); Move constructor. Implement as introduced in the lessons.
CircularLinkedList<T>& operator=(CircularLinkedList<T>&& source); Move assignment operator. Implement as introduced in the lessons.
Provided member functions
CircularLinkedList(); Default constructor: head_ and tail_ are initialized to the nullptr; node_order_ with Order::kASC.
~CircularLinkedList(); Destructor: frees all dynamically allocated objects comprising the singly linked list.
Data members
Node<T>* head_; Pointer to the head of the circularly linked list.
Node<T>* tail_; Pointer to the tail of the circularly linked list.
Order node_order_; Specifies whether elements in the circularly linked list are ordered in ascending (Order::kASC) or descending order (Order::kDESC).
#include <iostream> #include "circular-linked-list.hpp" int main() { }
#ifndef CIRCULAR_LINKED_LIST_HPP #define CIRCULAR_LINKED_LIST_HPP #include <iostream> #include "node.hpp" enum class Order { kASC, kDESC }; template <typename T> class CircularLinkedList { public: CircularLinkedList() = default; CircularLinkedList(CircularLinkedList<T>&& source); CircularLinkedList<T>& operator=(CircularLinkedList<T>&& source); ~CircularLinkedList(); template <typename U> friend std::ostream& operator<<(std::ostream& os, const CircularLinkedList<U>& cll); private: Node<T>* head_ = nullptr; Node<T>* tail_ = nullptr; Order node_order_ = Order::kASC; }; template <typename T> std::ostream& operator<<(std::ostream& os, const CircularLinkedList<T>& cll) { Node<T>* iter = cll.head_; // empty list condition if (iter == nullptr) { os << "Empty list"; return os; } // non-empty list condition do { os << iter->data << '\t'; iter = iter->next; } while (iter != cll.head_); return os; } template <typename T> CircularLinkedList<T>::~CircularLinkedList() { if (head_ == nullptr) return; // non-empty list condition Node<T>* original_head = head_; Node<T>* next = nullptr; do { next = head_->next; delete head_; head_ = next; } while (head_ != original_head); } #endif
#include "circular-linked-list.hpp"
#ifndef NODE_HPP #define NODE_HPP // DO NOT MODIFY THIS FILE: OUR GRADER USES THE ORIGINAL NODE.HPP PROVIDED TO YOU. // WE DO NOT COPY THIS FILE FROM YOUR WORKSPACE TO OUR AUTO-GRADER. template <typename T> struct Node { T data; Node<T>* next; Node(T data) : data(data), next(nullptr) {} Node(T data, Node<T>* next) : data(data), next(next) {} }; #endif