C++ Core Guidelines: Rules for Copy and Move

The rules for copy and move are quite obvious. But before I describe them I have to write about the two remaining rules for constructors. They are about delegating and inheriting constructors.

Here are the two remaining rules:

Constructor Rules

stork 1324371 1280

C.51: Use delegating constructors to represent common actions for all constructors of a class

Since C++11 a constructor can delegate its work to another constructor of the same class. This is the modern way in C++ to put common actions for all constructors in one constructor. In C++ code before C++11, you often used for such a job an init function.

class Degree{
public:
  Degree(int deg){                                           // (1)
    degree= deg % 360;
    if (degree < 0) degree += 360;
  }

  Degree(): Degree(0){}                                      // (2)

  Degree(double deg): Degree(static_cast<int>(ceil(deg))){}  // (3)

private:
  int degree;
};

 

The constructors (2) and (3) of the class Degree delegates all it's initialisation work to the constructor (1) which verifies its arguments. Invoking constructors recursively is undefined behaviour.

C.52: Use inheriting constructors to import constructors into a derived class that does not need further explicit initialization

If you can reuse constructors of the base class in the derived class do it. If you don't do it, you violate the DRY (Don't Repeat Yourself) principle.

class Rec {
    // ... data and lots of nice constructors ...
};

class Oper : public Rec {
    using Rec::Rec;
    // ... no data members ...
    // ... lots of nice utility functions ...
};

struct Rec2 : public Rec {
    int x;
    using Rec::Rec;
};

Rec2 r {"foo", 7};
int val = r.x;            // uninitialized  (1)  

 

There is a danger using inheriting constructors. If your derived class such as Rec2 has own members, they are uninitialised (1).

Copy and Move

movers 24402 1280

The chapter starts with a meta-rule. Values types, also known as types that behave like an int, should be copyable, but interfaces in class hierarchies not. The last rule C.67 refers to this meta-rule.

Here are the eight rules:

The first 6 rules for copy and move consist of 3 quite similar pairs; therefore, I can explain them together.

  • C.60 and C.63 state that you should make the copy (move) assignment non-virtual and return a non-const reference. There is a difference in the way, you should take the parameter.
    • Copy assignment should take its parameter by a const lvalue reference (&), because you should not change the source of your assignment
    • Move assignment should take its parameter by a non-const rvalue reference (&&), because you have to modify the source of your assignment
    • This is the pattern the assignment operators of the standard template library follow. Here is a simplified look at std::vector.
vector& operator=( const vector& other ); 	
vector& operator=( vector&& other );          // (since C++11, until C++17)
vector& operator=( vector&& other ) noexcept  // since C++17)
  • C.61 and C.64 say that a copy (move) operation should actually copy (move). This is the expected semantic for a = b.
  • In case of copying, this means that after copying  a and b (a = b) a must be the same: (a ==b).
  • Copying can be a deep or shallow. Deep copying means that both objects a and b are afterwards totally independent of each other (value semantic). Shallow copying means that both objects a and b share an object afterwards (reference semantic).
  • C.64 states the moved-from object should be in a valid state. Most of the times this is the default state of the source. The C++ standard requires, that the moved-from object must be afterwards in an unspecified but valid state.
  • C.62 and C.65 state the same. Copy (move) assignment should be safe for self-assignment. x = x should not change the value of x.
    • Copy (move) assignment of the containers of the STL, std::string and built- type such as int is safe for self-assignment; therefore, the default generated copy (move) assignment operator is in this case safe for self-assignment. The same will hold for an automatic generated copy (move) assignment operator which uses types that are safe for self-assignment.
class Foo {
    string s;
    int i;
public:
  Foo& Foo::operator=(const Foo& a){
    s = a.s;
    i = a.i;
    return *this;
  }
  Foo& Foo::operator=(Foo&& a) noexcept {
    s = std::move(a.s);
    i = a.i;
    return *this;
  }
// ....
};

The code snippet shows that there is no test for self-assignment such as in the next example necessary. Here is the version of the type Foo with redundant (expensive) checks (1) and (2) for self-assignment.
class Foo {
    string s;
    int i;
public:
  Foo& Foo::operator=(const Foo& a){
    if (this == &a) return *this;          // (1)
    s = a.s;
    i = a.i;
    return *this;
  }
  Foo& Foo::operator=(Foo&& a) noexcept {   // (2)
    if (this == &a) return *this;
    s = std::move(a.s);
    i = a.i;
    return *this;
  }
  // ....
};

C.66: Make move operations noexcept

Move operations should not throw; therefore, you should declare them as noexcept. You can implement your move constructor and move assignment operators that do not throw.

This is the pattern the move operators of the standard template library follow. Have a look at std::vector.

template<typename T>
class Vector {
    // ...
    Vector(Vector&& a) noexcept :elem{a.elem}, sz{a.sz} { a.sz = 0; a.elem = nullptr; }
    Vector& operator=(Vector&& a) noexcept { elem = a.elem; sz = a.sz; a.sz = 0; a.elem = nullptr; }
    // ...
public:
    T* elem;
    int sz;
};

 

The last rule C.67 deserves more attention.

C.67: A base class should suppress copying, and provide a virtual clone instead if “copying” is desired

The main reason for this rule is that slicing is not possible. Slicing is one of this phenomenon in C++, my colleagues always warned me. There exists also an article on Wikipedia about object slicing.

Slicing will happen when an object of a derived classe will be copied to an object of a base class.

struct Base { int base_; };
 
struct Derived : Base { int derived_; };
 
int main(){
  Derived d;
  Base b = d;   // slicing, only the Base parts of (base_) are copied
}

 

In this scenario, the copy operations of the base class are used; therefore, only the base part of d is copied.

From the object-oriented perspective an instance of Derived is-a  instance of Base. That means, whenever you need an instance of Base you can use an instance of Derived. But you have to be careful. If you take the instance of Base by copy (value-semantic), you will only get the base parts of an instance of Derived.

 

void needBase(Base b){ .... };

Derived der;
needBase(der);      // slicing kicks in

 

The cure which the guidelines suggest is: the base class should suppress copying but provide instead a virtual clone method if copying is desired. Here is the example from the guidelines.

class B { // GOOD: base class suppresses copying
    B(const B&) = delete;
    B& operator=(const B&) = delete;
    virtual unique_ptr<B> clone() { return /* B object */; }
    // ...
};

class D : public B {
    string more_data; // add a data member
    unique_ptr<B> clone() override { return /* D object */; }
    // ...
};

auto d = make_unique<D>();
auto b = d.clone(); // ok, deep clone

 

The clone method returns the newly created object in a std::unique_ptr; therefore, the ownership goes to the caller. Such a clone method is better known as a factory method. A factory method is one of the creational patterns from the book: Design Pattern: Elements of Reusable Object-Oriented Software.

What's next?

There are a few rules for default operations left. The next post deals with comparisons, swap, and hash.

 

 

Thanks a lot to my Patreon Supporters: Matt Braun, Roman Postanciuc, Tobias Zindl, Marko, G Prvulovic, Reinhold Dröge, Abernitzke, Frank Grimm, Sakib, Broeserl, António Pina, Darshan Mody, Sergey Agafyin, Андрей Бурмистров, Jake, GS, Lawton Shoemake, Animus24, Jozo Leko, John Breland, espkk, Wolfgang Gärtner,  Louis St-Amour, Stephan Roslen, Venkat Nandam, Jose Francisco, Douglas Tinkham, Kuchlong Kuchlong, Avi Kohn, Robert Blanch, Truels Wissneth, Kris Kafka, Mario Luoni, Neil Wang, Friedrich Huber, lennonli, Pramod Tikare Muralidhara, and Peter Ware.

 

Thanks in particular to Jon Hess, Lakshman, Christian Wittenhorst, Sherhy Pyton, Dendi Suhubdy, and Sudhakar Belagurusamy. 

 

Seminars

I'm happy to give online-seminars or face-to-face seminars world-wide. Please call me if you have any questions.

Bookable (Online)

Deutsch

Standard Seminars 

Here is a compilation of my standard seminars. These seminars are only meant to give you a first orientation.

New

Contact Me

Modernes C++,

RainerGrimmSmall

 

Tags: classes

Comments   

+1 #1 Rr 2017-09-21 07:27
There is a typo:

"The *clown* method returns" :)
Quote
+1 #2 climatizzatori 2017-10-06 15:49
fantastico articolo
Quote

My Newest E-Books

Course: Modern C++ Concurrency in Practice

Course: C++ Standard Library including C++14 & C++17

Course: Embedded Programming with Modern C++

Course: Generic Programming (Templates)

Course: C++ Fundamentals for Professionals

Subscribe to the newsletter (+ pdf bundle)

Blog archive

Source Code

Visitors

Today 51

Yesterday 8683

Week 8735

Month 175569

All 5472673

Currently are 170 guests and no members online

Kubik-Rubik Joomla! Extensions

Latest comments