C++ Core Guidelines: Declarations and Initialisations

Let's continue our tour through the rules for expressions and statements in the C++ core guidelines. This post will be about declarations and initialisations. 

 

creation of man

 

To be honest, the most of the rules in this post are quite obvious but they often provide one or the other very interesting insight; therefore, I will mainly write in this post about this special aspects. Here are the rules for today:

Here are the details. 

ES.11: Use auto to avoid redundant repetition of type names

The example from the guidelines is not promising to me. So, let me give you another one. If you use auto, changing your code may become a piece of cake. 

The following example is totally based on auto. You have not to think about the types and, therefore, you can not make an error. This means the type of res will be int at the end.

auto a = 5;
auto b = 10;
auto sum =  a * b * 3;
auto res = sum + 10; 
std::cout << typeid(res).name();         // i

 

If you decide to change the literal b from int to double (2), or use in (3) a float literal instead of the int literal. No problem. It will be automatically handled for you. 

 

auto a = 5;
auto b = 10.5;                           // (1)
auto sum = a * b * 3;
auto res = sum * 10;  
std::cout << typeid(res).name();         // d
  
auto a = 5;
auto b = 10;
auto sum = a * b * 3.1f;                // (2)
auto res = sum * 10;  
std::cout << typeid(res).name();        // f

ES.12: Do not reuse names in nested scopes

This is one of this quite obvious rules. For readability and maintenance reasons, you should not reuse names in nested scopes.

// shadow.cpp

#include <iostream>

int shadow(bool cond){
  int d = 0;
  if (cond){
    d = 1;
  }
  else {
    int d = 2;
    d = 3;
  }
  return d;
}

int main(){

    std::cout << std::endl;
    
    std::cout << shadow(true) << std::endl;      
    std::cout << shadow(false) << std::endl;     

    std::cout << std::endl;
    
}

 

What will be the output of the program? Confused by the d's? Here is the result.

shadowD

This was easy! Right? But the same phenomena is quite surprising in class hierarchies.

// shadowClass.cpp

#include <iostream>
#include <string>

struct Base{
    void shadow(std::string){                           // 2
        std::cout << "Base::shadow" << std::endl;       
    }
};

struct Derived: Base{
    void shadow(int){                                   // 3
        std::cout << "Derived::shadow" << std::endl;    
    }
};

int main(){
    
    std::cout << std::endl;
    
    Derived derived;
    
    derived.shadow(std::string{});                      // 1
    derived.shadow(int{});                              
    
    std::cout << std::endl;
    
}

 

Both structs Base and Derived have a method shadow. The one in the base accepts an std::string (2) and the other one an int (3). When I invoke the object derived with a default-constructed std::string (1), I may assume that the base version will be called. Wrong! Because the method shadow is implemented in the class Derived the methods of the base class will not be considered during name resolution. Here is the output of my gcc.

shadowClass

To fix this issue, shadow must be known to Derived

struct Derived: Base{
    using Base::shadow;              // 1
    void shadow(int){
        std::cout << "Derived::shadow" << std::endl;   
    }
};

 

You have to put a Base::shadow (1) into Derived. Now the program behaves as expected.

shadowClassFixed

ES.20: Always initialize an object

The rules which object will be initialised or not are quite difficult to get right in C++. Here is a simple example.

struct T1 {};
class T2{
 public:
    T2() {} 
};

int n;                 // OK

int main(){
  int n2;              // ERROR
  std::string s;       // OK
  T1 t1;               // OK
  T2 t2;               // OK                  
}

 

n is a global variable; therefore, it will be initialised to 0. This will not hold for n2, because it is a local variable and will, therefore, not be initialised. But if you use a user-defined type such as std::string, T1, or T2 in a local scope they will be initialised.

If that is to difficult for you, I have a simple fix. Use auto. Now, you can not forget to initialise the variable. The compiler will check this.

struct T1 {};
class T2{
 public:
    T2() {}
};

auto n = 0;

int main(){
  auto n2 = 0;
  auto s = ""s;      
  auto t1 = T1();               
  auto t2 = T2();
}

 

ES.21: Don’t introduce a variable (or constant) before you need to use it

I think this is trivial. We program C++, not C.

ES.22: Don’t declare a variable until you have a value to initialize it with

If you don't follow this rule, you may have a so-called used-before-set error. Have a look at the guidelines.

 

int var;  

if (cond)      // some non-trivial condition
    Set(&var);
else if (cond2 || !cond3) {
    var = Set2(3.14);
}

// use var

 

Do you know, if one of the conditions hold? If not, var as a local built-in variable is used but not initialised.

ES.23: Prefer the {}-initializer syntax

There are a lot of reasons for using {}-initialisation:

  • always applicable
  • overcomes the most vexing parse
  • prevents narrowing

You have just to keep a special rule in mind. If you use auto in combination with an {}-initialisation, you will get an std::initializer_list in C++14 but not in C++17. 

For all the details read my previous post to {}-Initialisation.

ES.24: Use a unique_ptr<T> to hold pointers

I will make it short. An std::unique_ptr<T> is by design as efficient as a raw pointer but has a great added value: it takes care of its resource. This means: don't use a raw pointer. If you are curious about the details of std::unique_ptr, read my two posts to std::unqiue_ptr.

What's next?

We are not done with the rules for declarations in C++. The remaining will follow in the next post.

 

 

 

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

 

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 240

All 649045

Currently are 167 guests and no members online