More Myths of My Blog Readers

Today, I conclude my story to your myths about C++. These myths are around function parameters, the initialisation of class members, and pointer versus references.

 dragon 4417431 1280

Always take the parameter by const reference (Gunter Königsmann)

When a function takes its parameter and doesn't want to modify it, you have two options.

  • Take the parameter by value (copy it)
  • Take the parameter by const reference

This was the correctness perspective, but what can be said about the performance. The C++ core guidelines is specific about performance. Let's look at the following example.

void f1(const string& s);  // OK: pass by reference to const; always cheap

void f2(string s);         // bad: potentially expensive

void f3(int x);            // OK: Unbeatable

void f4(const int& x);     // bad: overhead on access in f4()

 

Presumably, based on experience, the guidelines states a rule of thumb:

  • You should take a parameter p by const reference if sizeof(p) > 4 * sizeof(int)
  • You should copy a parameter p if sizeof(p) < 3 * sizeof(int)

Okay, now you should know how big your data types are. The program sizeofArithmeticTypes.cpp gives the answers for arithmetic types.

// sizeofArithmeticTypes.cpp

#include <iostream>

int main(){
  
    std::cout << std::endl;
    
    std::cout << "sizeof(void*): " << sizeof(void*) << std::endl;  
    
    std::cout << std::endl;
 
    std::cout << "sizeof(5):  "  << sizeof(5)   << std::endl;
    std::cout << "sizeof(5l): "  << sizeof(5l)  << std::endl;
    std::cout << "sizeof(5ll): " << sizeof(5ll) << std::endl;
    
    std::cout << std::endl;
    
    std::cout << "sizeof(5.5f): " << sizeof(5.5f) << std::endl;
    std::cout << "sizeof(5.5): "  << sizeof(5.5)  << std::endl; 
    std::cout << "sizeof(5.5l): " << sizeof(5.5l) << std::endl;       
    
    std::cout << std::endl;
            
}

 

sizeof(void*) returns if it is a 32-bit or a 64-bit system. Thanks to online compiler rextester, I can execute the program with GCC, Clang, and cl.exe (Windows). Here are the numbers for all 64-bit systems.

GCC

sizeofGCC

Clang

sizeofClang

cl.exe (Windows)

sizeofVC

cl.exe behaves differently to GCC and Clang. A long int has only 4 bytes, and a long double has 8 bytes. On GCC and Clang, long int and long double have the double size.

To decide, when to take the parameter by value or by const reference is just math. If you want to know the exact performance numbers for your architecture, there is only one answer: measure.

Initialisation and Assignment in the Constructor are equivalent (Gunter Königsmann)

First, let me show you initialisation and assignment in the constructor.

class Good{  
    int i;
public:
    Good(int i_): i{i_}{} 
};

class Bad{  
    int i;
public:
    Bad(int i_): { i = i_; } 
};

 

The class Good uses initialisation but the class Bad assignment. The consequences are:

  • The variable i is directly initialised in the class Good
  • The variable i is default constructed and then assigned to in the class Bad

 The constructor initialisation is, on one hand, slower but does not work on the other hand for const members, references, or members which can not be default-constructed possible.

// constructorAssignment.cpp

struct NoDefault{
    NoDefault(int){};
};

class Bad{
    const int constInt;
    int& refToInt;
    NoDefault noDefault;
public:
    Bad(int i, int& iRef){
        constInt = i;
        refToInt = iRef;
    }
    // Bad(int i, int& iRef): constInt(i), refToInt(iRef), noDefault{i} {}
};

int main(){
    
    int i = 10;
    int& j = i;
  
    Bad bad(i, j);
  
}

 

When I try to compile the program, I get three different errors.

  1. constInt is not initialised and can not be assigned in the constructor.
  2. refToInt is not initialised.
  3. The class NoDefault has no default constructor because I implemented one constructor for int. When you implement a constructor, the compiler will not automatically generate a default constructor.

 

constructorAssignment

In the second successful compilation, I used the second commented out constructor which uses initialisation instead of assignment.

The example used references instead of raw pointers for a good reason.

You need Raw Pointers in your Code (Thargon110)

Motivated by a comment from Thargon110, I want to be dogmatic: NNN. What? I mean No Naked New. From an application perspective, there is no reason to use raw pointers. If you need pointer like semantic, put your pointer into a smart pointer (You see: NNN) and you are done.

In essence C++11 has a std::unique_ptr for exclusive ownership and a std::shared_ptr for shared ownership. Consequently, when you copy a std::shared_ptr, the reference counter is incremented, and when you delete the std::shared_ptr, the reference counter is decremented. Ownership means, that the smart pointer keeps track of the underlying memory and releases the memory if it is not necessary any more. The memory is not necessary any more in the case of the std::shared_ptr when the reference counter becomes 0.

So memory leaks are gone with modern C++. Now I hear your complaints. I'm happy to destroy them.

  • Cycles of std::shared_ptr can create a memory leak because the reference counter will not become 0. Right, put a std::weak_ptr in-between to break the cyclic reference: std::weak_ptr.
  • A std::shared_ptr has a management overhead and is, therefore, more expensive than a raw pointer. Right, use a std::unique_ptr.
  • A std::unique_ptr is not comfortable enough because it can't be copied. Right, but a std::unique_ptr can be moved.

 The last complaint is quite dominant. The small example should make my point:

 

// moveUniquePtr.cpp

#include <algorithm>
#include <iostream>
#include <memory>
#include <utility>
#include <vector>

void takeUniquePtr(std::unique_ptr<int> uniqPtr){          // (1)
    std::cout << "*uniqPtr: " << *uniqPtr << std::endl;
}

int main(){
  
    std::cout << std::endl;
  
    auto uniqPtr1 = std::make_unique<int>(2014);
    
    takeUniquePtr(std::move(uniqPtr1));                    // (1)
    
    auto uniqPtr2 = std::make_unique<int>(2017);
    auto uniqPtr3 = std::make_unique<int>(2020);
    auto uniqPtr4 = std::make_unique<int>(2023);
    
    std::vector<std::unique_ptr<int>> vecUniqPtr;
    vecUniqPtr.push_back(std::move(uniqPtr2));             // (2)
    vecUniqPtr.push_back(std::move(uniqPtr3));             // (2)
    vecUniqPtr.push_back(std::move(uniqPtr4));             // (2)
    
    std::cout << std::endl;
    
    std::for_each(vecUniqPtr.begin(), vecUniqPtr.end(),    // (3)
                  [](std::unique_ptr<int>& uniqPtr){ std::cout <<  *uniqPtr << std::endl; } );
    
    std::cout << std::endl;
    
}

 

The function takeUniquePtr in line (1) takes a std::unique_ptr by value. The key observation is that you have to move the std::unique_ptr inside. The same argument holds for the std::vector<std::unique_ptr<int>> (line 2). std::vector as all containers of the standard template library wants to own its elements but to copy a std::unique_ptr is not possible. std::move solves this issue. You can apply an algorithm such as std::for_each on the std::vector<std::unique_ptr<int>> (line 3) if no copy semantic is used.

Use References instead of Raw Pointers

On the end, I want to refer to the key concern of Thargon110. Admittedly, this rule is way more import in classical C++ without smart pointers because smart pointers are in contrast to raw pointers owners.

Use a reference instead of a pointer because a reference has always a value. Boring checks such as the following one are gone with references. 

 

if(!ptr){
   std::cout << "Something went terrible wrong" << std::endl;
   return;
}
std::cout << "All fine" << std::endl;

 

Additionally, you can forget the check. References behave just as constant pointers.

What's next?

The C++ core guidelines defines profiles. Profiles are subset of rules. They exist for type safety, bounds safety, and lifetime safety. They will be my next topic.

 

 

Thanks a lot to my Patreon Supporters: Paul Baxter,  Meeting C++, Matt Braun, Roman Postanciuc, Venkata Ramesh Gudpati, Tobias Zindl, Marko, Ramesh Jangama, G Prvulovic, Reiner Eiteljörge, Benjamin Huth, Reinhold Dröge, Timo, Abernitzke, Richard Ohnemus , Frank Grimm, Sakib, Broeserl, and António Pina.  

 

Thanks in particular to:
 
crp4

 

   

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 600 pages full of modern C++ and more than 100 source files presenting concurrency in practice.

 

Get your interactive course

 

Modern C++ Concurrency in Practice

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

educative CLibrary

Based on my book "Concurrency with Modern C++" educative.io created an interactive course.

What's Inside?

  • 140 lessons
  • 110 code playgrounds => Runs in the browser
  • 78 code snippets
  • 55 illustrations

Based on my book "The C++ Standard Library" educative.io created an interactive course.

What's Inside?

  • 149 lessons
  • 111 code playgrounds => Runs in the browser
  • 164 code snippets
  • 25 illustrations

Add comment


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)

Subscribe to the newsletter (+ pdf bundle)

Blog archive

Source Code

Visitors

Today 872

All 2910045

Currently are 151 guests and no members online

Kubik-Rubik Joomla! Extensions

Latest comments