C++ Core Guidelines: More Rules about Performance

Contents[Show]

In this post, I continue my journey through the rules to performance in the C++ Core Guidelines.  I will mainly write about design for optimization.

Here are the two rules for today. 

Per.7: Design to enable optimization

When I read this title, I immediately have to think about move semantics. Why? Because you should write your algorithms with move semantics and not with copy semantics. You will automatically get a few benefits. 

  1. Of course, your algorithms use a cheap move instead of an expensive copy. 
  2. Your algorithm is way more stable because it requires no memory, and you will get no std::bad_alloc exception.
  3. You can use your algorithm with move-only types such as std::unique_ptr

Understood! Let me implement a generic swap algorithm that uses move semantics.

// swap.cpp

#include <algorithm>
#include <cstddef> 
#include <iostream>
#include <vector>

template <typename T>                                                // (3)
void swap(T& a, T& b) noexcept {
    T tmp(std::move(a));
    a = std::move(b);
    b = std::move(tmp);
}

class BigArray{

public:
    BigArray(std::size_t sz): size(sz), data(new int[size]){}

    BigArray(const BigArray& other): size(other.size), data(new int[other.size]){
        std::cout << "Copy constructor" << std::endl;
        std::copy(other.data, other.data + size, data);
    }
    
    BigArray& operator=(const BigArray& other){                      // (1)
        std::cout << "Copy assignment" << std::endl;
        if (this != &other){
            delete [] data;
            data = nullptr;
			
            size = other.size;
            data = new int[size];
            std::copy(other.data, other.data + size, data);
        }
        return *this;
    }
    
    ~BigArray(){
        delete[] data;
    }
private:
    std::size_t size;
    int* data;
};

int main(){

  std::cout << std::endl;

  BigArray bigArr1(2011);
  BigArray bigArr2(2017);
  swap(bigArr1, bigArr2);                                           // (2)

  std::cout << std::endl;

};

 

Fine. That was it. No! My coworker gave me his type BigArray. BigArray has a few flaws. I will write about the copy assignment operator (1) later. First of all, I have a more serious concern. BigArray does not support move semantics but only copy semantics. What will happen if I swap the BigArrays in line (2)?  My swap algorithm uses move semantic (3) under the hood. Let's try it out.

swap

 

Nothing bad will happen. Traditional copy semantics will kick in, and you will get the classical behavior. Copy semantics is a kind of fallback to move semantics. You can see it the other way around. The move is an optimized copy. 

How is that possible? I asked for a move operation in my swap algorithm. The reason is that std::move returns a rvalue. A const lvalue reference can bind to an rvalue, and the copy constructor or a copy assignment operator takes a const lvalue reference. If BigArray had a move constructor or a move assignment operator taking rvalue references, both would have higher priority than the copy pendants.  

Implementing your algorithms with move semantic means that move semantic will automatically kick in if your data types support it. If not, copy semantics will be used as a fallback. In the worst case, you will have classical behavior.

I said the copy assignment operator has a few flaws. Here are they:

BigArray& operator=(const BigArray& other){                      
    if (this != &other){                                 // (1)
        delete [] data;                                        
        data = nullptr;
			
        size = other.size;
        data = new int[size];                            // (2)
        std::copy(other.data, other.data + size, data);  // (3)
    }
    return *this;
}

 

  1. I have to check for self-assignment. Most of the time, self-assignment will not happen, but I always check for the special case.
  2. If the allocation fails, this was already modified. The size is wrong, and the data is already deleted. This means the copy constructor only guarantees the basic exception guarantee but not the strong one. The basic exception guarantee states that there is no leak after an exception. The strong exception guarantees that the program can be rolled back to the state in case of an exception. Read the Wikipedia article about exception safety for more details on exception safety.
  3. The line is identical to the line in the copy constructor.

You can overcome these flaws by implementing your swap function. This is already suggested by the C++ Core Guidelines: C.83: For value-like types, consider providing a noexcept swap function. Here is the new BigArray having a non-member swap function and a copy assignment operator using the swap function. 

class BigArray{

public:
    BigArray(std::size_t sz): size(sz), data(new int[size]){}

    BigArray(const BigArray& other): size(other.size), data(new int[other.size]){
        std::cout << "Copy constructor" << std::endl;
        std::copy(other.data, other.data + size, data);
    }
	
    BigArray& operator = (BigArray other){                  // (2)
        swap(*this, other);                                 
        return *this;
    }
    
    ~BigArray(){
        delete[] data;
    }
	
    friend void swap(BigArray& first, BigArray& second){    // (1)
        std::swap(first.size, second.size);
        std::swap(first.data, second.data);
    }
	
private:
    std::size_t size;
    int* data;
};

 

The swap function inline (1) is not a member; therefore, a call swap(bigArray1, bigArray2) uses it.  The signature of the copy assignment operator in line (2) may surprise you. Because of the copy, no self-assignment test is necessary. Additionally, the strong exception guarantee holds, and there is no code duplication. This technique is called the copy-and-swap idiom.

There are a lot of overloaded versions of std::swap available. The C++ standard provides about 50 overloads. 

 

Rainer D 6 P2 540x540Modernes C++ Mentoring

Be part of my mentoring programs:

 

 

 

 

Do you want to stay informed about my mentoring programs: Subscribe via E-Mail.

Per.10: Rely on the static type system

This is a kind of meta-rule in C++. Catch errors at compile-time. I can make my explanation of this rule relatively short because I have already written a few articles on this critical topic:

  • Use automatic type deduction with auto (automatically initialized) combined with {}-initialization, and you will get many benefits.
    1. The compiler always knows the right type: auto f = 5.0f.
    2. You can never forget to initialize a type: auto a; will not work.
    3. You can verify with {}-initialization that no narrowing conversion will kick in; therefore, you can guarantee that the automatically deduced type is the type you expected: int i = {f}; The compiler will check in this expression that f is, in this case, an int. If not, you will get a warning. This will not happen without braces: int i = f;.
  • Check with static_assert and the type-traits library type properties at compile time. If the check fails, you will get a compile-time error: static_assert<std::is_integral<T>::value, "T should be an integral type!").
  • Make type-safe arithmetic with the user-defined literals and the new built-in literals(user-defined literals): auto distancePerWeek=  (5 * 120_km + 2 * 1500m - 5 * 400m) / 5;.  
  • override and final provide guarantees to virtual methods. The compiler checks with override that you overrode a virtual method. The compiler guarantees further with final that you can not override a virtual method that is declared final
  • The New Null Pointer Constant nullptr cleans in C++11 up with the ambiguity of 0 and the macro NULL.

What's next?

My journey through the rules to performance will go on. In the next post, I will, in particular, write about how to move computation from runtime to compile-time and how you should access memory.

 

 

 

Thanks a lot to my Patreon Supporters: Matt Braun, Roman Postanciuc, Tobias Zindl, G Prvulovic, Reinhold Dröge, Abernitzke, Frank Grimm, Sakib, Broeserl, António Pina, Sergey Agafyin, Андрей Бурмистров, Jake, GS, Lawton Shoemake, Animus24, Jozo Leko, John Breland, Venkat Nandam, Jose Francisco, Douglas Tinkham, Kuchlong Kuchlong, Robert Blanch, Truels Wissneth, Kris Kafka, Mario Luoni, Friedrich Huber, lennonli, Pramod Tikare Muralidhara, Peter Ware, Daniel Hufschläger, Alessandro Pezzato, Bob Perry, Satish Vangipuram, Andi Ireland, Richard Ohnemus, Michael Dunsky, Leo Goodstadt, John Wiederhirn, Yacob Cohen-Arazi, Florian Tischler, Robin Furness, Michael Young, Holger Detering, Bernd Mühlhaus, Matthieu Bolt, Stephen Kelley, Kyle Dean, Tusar Palauri, Dmitry Farberov, Juan Dent, George Liao, Daniel Ceperley, Jon T Hess, Stephen Totten, Wolfgang Fütterer, Matthias Grün, Phillip Diekmann, Ben Atakora, Ann Shatoff, and Rob North.

 

Thanks, in particular, to Jon Hess, Lakshman, Christian Wittenhorst, Sherhy Pyton, Dendi Suhubdy, Sudhakar Belagurusamy, Richard Sargeant, Rusty Fleming, John Nebel, Mipko, Alicja Kaminska, and Slavko Radman.

 

 

My special thanks to Embarcadero CBUIDER STUDIO FINAL ICONS 1024 Small

 

My special thanks to PVS-Studio PVC Logo

 

My special thanks to Tipi.build tipi.build logo

 

My special thanks to Take Up Code TakeUpCode 450 60

 

Seminars

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

Bookable (Online)

German

Standard Seminars (English/German)

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

  • C++ - The Core Language
  • C++ - The Standard Library
  • C++ - Compact
  • C++11 and C++14
  • Concurrency with Modern C++
  • Design Pattern and Architectural Pattern with C++
  • Embedded Programming with Modern C++
  • Generic Programming (Templates) with C++

New

  • Clean Code with Modern C++
  • C++20

Contact Me

Modernes C++,

RainerGrimmDunkelBlauSmall

Stay Informed about my Mentoring

 

Mentoring

English 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

Course: The All-in-One Guide to C++20

Course: Master Software Design Patterns and Architecture in C++

Subscribe to the newsletter (+ pdf bundle)

All tags

Blog archive

Source Code

Visitors

Today 4194

Yesterday 4344

Week 41072

Month 21318

All 12099527

Currently are 154 guests and no members online

Kubik-Rubik Joomla! Extensions

Latest comments