C++ Core Guidelines: Rules about Don'ts

Contents[Show]

This post is about don'ts. Here are the two most important rules of this post: Don't use std::move thoughtless and don't slice. Let's start.

 Black Forest gateau20

 

Here are the don'ts for today.

The first rule is a disguised don't. 

ES.56: Write std::move() only when you need to explicitly move an object to another scope

Most of the times there is no need to explicitly call std::move.The compiler automatically applies move semantic if the source of the operation is a rvalue. A rvalue is an object with no identity. A rvalue has typically no name and you can not get its address. The remaining objects are lvalues. 

Applying std::move to a lvalue gives most of the times an empty object. The lvalue is afterwards in a so-called moved-from state. This means that it is in a valid but no nearer specified state. Sound strange? Right! You have just keep this rule in mind: After you move from a lvalue such as std::move(source) you can not make any assumption about source. You have to set it to a new value.

Wait a second. The rules says you should only use std::move if you want to move an object to another scope. The classical use-cases are objects which can not be copied but moved. For example, you want to move an std::promise into another thread. 

// moveExplicit.cpp

#include <future>
#include <iostream>
#include <thread>
#include <utility>

void product(std::promise<int>&& intPromise, int a, int b){     // (1)
  intPromise.set_value(a * b);
}

int main(){

  int a= 20;
  int b= 10;

  // define the promises
  std::promise<int> prodPromise;

  // get the futures
  std::future<int> prodResult= prodPromise.get_future();

  // calculate the result in a separat thread
  std::thread prodThread(product,std::move(prodPromise), a, b);   // (2)
 
  // get the result
  std::cout << "20 * 10 = " << prodResult.get() << std::endl;     // 200
  
  prodThread.join();

}

 

The function product (1) gets the std::promise by rvalue reference. A promise cannot be copied but moved; therefore, std::move is necessary (2) to move the promise into the newly created thread. 

Here is the big don't! Don't use std::move in a return statement. 

vector<int> make_vector() {
    vector<int> result;
    // ... load result with data
    return std::move(result);       // bad; just write "return result;"
}

 

Trust your optimiser! If you return the object just by copy, the optimiser will do its job. This is best practices until C++14; this is an obligatory rule since C++17 and is called guaranteed copy elision. Although this technique is called automatic copy elision, move operations are also optimised away with C++11.

RVO stands for Return Value Optimisation and means, that the compiler is allowed to remove unnecessary copy operations. What was until C++14 a possible optimisation step becomes in C++17 a guarantee.

MyType func(){
  return MyType{};         // (1) no copy with C++17
}
MyType myType = func();    // (2) no copy with C++17

 

Two unnecessary copy operations can happen in this few lines. The first one in (1) and the second one in (2). With C++17, both copy operations are not allowed.

If the return value has a name its called NRVO. This acronym stands for Named Return Value Optimization.

 

MyType func(){
  MyType myVal;
  return myVal;            // (1) one copy allowed 
}
MyType myType = func();    // (2) no copy with C++17

 

The subtle difference is that the compiler can still copy the value myValue according to C++17 (1). But no copy will take place in (2). 

ES.60: Avoid new and delete outside resource management functions

 Okay, I can make it short. Don't use new and delete in the application code. This rule has a nice reminder: "No naked new!". 

ES.61: Delete arrays using delete[] and non-arrays using delete

Here is the rationale for the last rule. Resource management in application code is error-prone.

void f(int n)
{
    auto p = new X[n];   // n default constructed Xs
    // ...
    delete p;   // error: just delete the object p, rather than delete the array p[]
}

 

The guidelines states in the comment: "just delete the object p". Let me put it more drastically. This is undefined behaviour!

ES.63: Don’t slice

First of all. What is slicing? Slicing means: you want to copy an object during assignment or initialisation and you get only a part of the object. 

Let's start simple.

// slice.cpp

struct Base { 
  int base{1998};
}
 
struct Derived : Base { 
  int derived{2011};
}

void needB(Base b){
    // ...
}
 
int main(){

  Derived d;
  Base b = d;              // (1)
  Base b2(d);              // (2)
  needB(d);                // (3)

}

 

The lines (1), (2), and (3) have all the same effect: the Derived part of d is removed. I assume that was not your intention.

I said in the announcement to this post that slicing is one of the darkest parts of C++. Now it becomes dark.

 

// sliceVirtuality.cpp

#include <iostream>
#include <string>

struct Base { 
    virtual std::string getName() const {        // (1)
        return "Base";       
    }
};
 
struct Derived : Base { 
    std::string getName() const override {       // (2)
        return "Derived";
    }
};
 
int main(){
    
    std::cout << std::endl;
    
    Base b;
    std::cout << "b.getName(): " << b.getName() << std::endl;       // (3)
    
    Derived d;
    std::cout << "d.getName(): " << d.getName() << std::endl;       // (4)
    
    Base b1 = d;
    std::cout << "b1.getName():  " << b1.getName() << std::endl;    // (5)
   
    Base& b2 = d;
    std::cout << "b2.getName():  " << b2.getName() << std::endl;    // (6)

    Base* b3 = new Derived;
    std::cout << "b3->getName(): " << b3->getName() << std::endl;   // (7)
    
    std::cout << std::endl;

}

 

I created a small hierarchy consisting of the Base and the Derived class. Each object of this class hierarchy should return its name. I made the method getName virtual (1) and overrode it in (2); therefore, I will have polymorphism. This means I can use a derived object via a reference (6) or a pointer to a base object (7). Under the hood, the object is of type Derived

This will not hold, if I just copy Derived d to Base b1 (5). In this case, slicing kicks in and I have a Base object under the hood. In case of copying, the declared or static type is used. If you use an indirection such as a reference or a pointer, the actual or dynamic type is used. 

sliceVirtuality

To keep the rule in mind is quite simple: If your instances of a class should be polymorphic, it should declare or inherit at least one virtual method and you should use its objects via an indirection such as a pointer or a reference. 

Of course, there is a cure against slicing: provide a virtual clone function. Read the details here: C++ Core Guidelines: Rules for Copy and Move.

What's next

This post was about don'ts. The next post will start with a do. Use curly braces for initialisation of data. 

 

 

 

Thanks a lot to my Patreon Supporters: Eric Pederson, Paul Baxter, Carlos Gomes Martinho, and SAI RAGHAVENDRA PRASAD POOSA.

 

 

Get your e-book at leanpub:

The C++ Standard Library

 

Concurrency With Modern C++

 

Get Both as one Bundle

cover   ConcurrencyCoverFrame   bundle
With C++11, C++14, and C++17 we got a lot of new C++ libraries. In addition, the existing ones are greatly improved. The key idea of my book is to give you the necessary information to the current C++ libraries in about 200 pages.  

C++11 is the first C++ standard that deals with concurrency. The story goes on with C++17 and will continue with C++20.

I'll give you a detailed insight in the current and the upcoming concurrency in C++. This insight includes the theory and a lot of practice with more the 100 source files.

 

Get my books "The C++ Standard Library" (including C++17) and "Concurrency with Modern C++" in a bundle.

In sum, you get more than 550 pages full of modern C++ and more than 100 source files presenting concurrency in practice.

 

Add comment


My Newest E-Books

Latest comments

Subscribe to the newsletter (+ pdf bundle)

Blog archive

Source Code

Visitors

Today 443

All 897190

Currently are 230 guests and no members online

Kubik-Rubik Joomla! Extensions