C++ Core Guidelines: More Non-Rules and Myths

Demystifying non-rules and myths in C++ is a laborious but absolutely necessary job. The goal is simple: use the powerful tool C++ appropriately.

 sao jorge 1503439 1280

By the way, did you noticed that my family name qualifies me, in particular, to write about this demystification? Anyway, here are the rules from the C++ core guidelines for today.

NR.5: Don’t: Don’t do substantive work in a constructor; instead use two-phase initialization

Obviously, this is the job a constructor: After the constructor is executed you should have a fully-initialised object. For that reason, the following code snippet from the guidelines is bad. 

class Picture
{
    int mx;
    int my;
    char * data;
public:
    Picture(int x, int y)
    {
        mx = x,
        my = y;
        data = nullptr;
    }

    ~Picture()
    {
        Cleanup();
    }

    bool Init()
    {
        // invariant checks
        if (mx <= 0 || my <= 0) {
            return false;
        }
        if (data) {
            return false;
        }
        data = (char*) malloc(x*y*sizeof(int));
        return data != nullptr;
    }

    void Cleanup()                    // (2)
    {
        if (data) free(data);
        data = nullptr;
    }
};

Picture picture(100, 0); // not ready-to-use picture here
// this will fail..                   // (1)
if (!picture.Init()) {
    puts("Error, invalid picture");
}
// now have a invalid picture object instance.

picture(100, 0) is not fully initialised and, therefore, all operation on the picture in line (1) operate on an invalid picture. The solution to this problem is as simple as effective: put all initialisation into the constructor.

 

class Picture
{
    size_t mx;
    size_t my;
    vector<char> data;

    static size_t check_size(size_t s)
    {
        // invariant check
        Expects(s > 0);
        return s;
    }

public:
    // even more better would be a class for a 2D Size as one single parameter
    Picture(size_t x, size_t y)
        : mx(check_size(x))
        , my(check_size(y))
        // now we know x and y have a valid size
        , data(mx * my * sizeof(int)) // will throw std::bad_alloc on error
    {
        // picture is ready-to-use
    }
    // compiler generated dtor does the job. (also see C.21)
};

 

Additionally, data is in the second example a std::vector and not a raw pointer. This means the Cleanup function (line 2) from the first example is not necessary anymore because the compiler will automatically clean up. Thanks to the static function check_size, the constructor can validate its arguments. But this is not the end of the benefits modern C++ gives up. 

Often you use constructors to set the default behaviour of an object. Don't do it. Directly set the default behaviour of an object in the class body. For example, compare the following classes Widget and WidgetImpro.

 

// classMemberInitialiserWidget.cpp

#include <iostream>

class Widget{
  public:
    Widget(): width(640), height(480), frame(false), visible(true) {}
    explicit Widget(int w): width(w), height(getHeight(w)), frame(false), visible(true){}
    Widget(int w, int h): width(w), height(h), frame(false), visible(true){}

    void show(){ std::cout << std::boolalpha << width << "x" << height
                           << ", frame: " << frame << ", visible: " << visible
                           << std::endl;
     }
  private:
    int getHeight(int w){ return w*3/4; }
    int width;
    int height;
    bool frame;
    bool visible;
};

class WidgetImpro{
  public:
    WidgetImpro(){}
    explicit WidgetImpro(int w): width(w), height(getHeight(w)){}
    WidgetImpro(int w, int h): width(w), height(h){}

    void show(){ std::cout << std::boolalpha << width << "x" << height
                           << ", frame: " << frame << ", visible: " << visible
                           << std::endl;
    }

  private:
    int getHeight(int w){ return w * 3 / 4; }
    int width = 640;
    int height = 480;
    bool frame = false;
    bool visible = true;
};


int main(){

  std::cout << std::endl;

  Widget wVGA;
  Widget wSVGA(800);
  Widget wHD(1280, 720);

  wVGA.show();
  wSVGA.show();
  wHD.show();

  std::cout << std::endl;

  WidgetImpro wImproVGA;
  WidgetImpro wImproSVGA(800);
  WidgetImpro wImproHD(1280, 720);

  wImproVGA.show();
  wImproSVGA.show();
  wImproHD.show();

  std::cout << std::endl;

}

 

Both classes behave the same.

 classMemberInitialiserWidget

The difference is that the constructors for the class WidgetImpro are way more comfortable to use and to extend. When you add a new variable to both classes, you have in the case of WidgetImpro only to edit one place, but each constructor in the case of the class Widget class is affected. Here is the picture I have in mind when I design a new class: Define the default behaviour of each object in the class body. Use explicit constructors to vary the default behaviour.

Done? No!

Often you use an init function to put common initialisation or validation stuff into one place. Fine, you follow the important DRY (Don't Repeat Yourself) principle, but you automatically break the other important principle, that you object should be fully initialised after the constructor call. How can you solve this riddle? Quite easy. Since C++11 we have constructor delegation. This means to put the common initialisation and validation stuff into one smart constructor and use the other constructors as kind of wrapper-constructors. Here is my idea translated to code.

 

// constructorDelegation.cpp

#include <cmath>
#include <iostream>

class Degree{
public:
  explicit Degree(int deg){                // (2)
    degree = deg % 360;
    if (degree < 0) degree += 360;
  }
  
  Degree() = default;
                                          // (3)
  explicit Degree(double deg):Degree(static_cast<int>(ceil(deg))) {}  

  int getDegree() const { return degree; }

private:
  int degree{};                           // (1)
};

int main(){

  std::cout << std::endl;

  Degree degree;
  Degree degree10(10);
  Degree degree45(45);
  Degree degreeMinus315(-315);
  Degree degree405(405);
  Degree degree44(44.45);

  std::cout << "Degree(): " << degree.getDegree() << std::endl;
  std::cout << "Degree(10): " << degree10.getDegree() << std::endl;
  std::cout << "Degree(45): " << degree45.getDegree() << std::endl;
  std::cout << "Degree(-315): " << degreeMinus315.getDegree() << std::endl;
  std::cout << "Degree(405): " << degree405.getDegree() << std::endl;
  std::cout << "Degree(44.45): " << degree44.getDegree() << std::endl;

  std::cout << std::endl;

}

 

The expression int degree{} (line) 1 value-initialises the degree to 0. The constructor in line 2 is quite smart. It transforms each degree to the unit circle. The constructor, taking a double, uses this constructor. For completeness, here is the output of the program:

constructorDelegation

NR.6: Don’t: Place all cleanup actions at the end of a function and goto exit

Okay, we can do better as the following code from the guidelines:

 

void do_something(int n)
{
    if (n < 100) goto exit;
    // ...
    int* p = (int*) malloc(n);
    // ...
exit:
    free(p);
}

 

By the way. Do you spot the error? The jump goto exit bypasses the definition of the pointer p.

What I often saw in legacy C-code was code structures like this.

// lifecycle.c

#include <stdio.h>
void initDevice(const char* mess){ printf("\n\nINIT: %s\n",mess); } void work(const char* mess){ printf("WORKING: %s",mess); } void shutDownDevice(const char* mess){ printf("\nSHUT DOWN: %s\n\n",mess); } int main(void){ initDevice("DEVICE 1"); work("DEVICE1"); { initDevice("DEVICE 2"); work("DEVICE2"); shutDownDevice("DEVICE 2"); } work("DEVICE 1"); shutDownDevice("DEVICE 1"); return 0; }

 

This is very error-prone but also typical code. Each usage of the device consists of three steps: initialisation, usage, and release of the device. Honestly, this is the job of RAII.

 

// lifecycle.cpp

#include <iostream>
#include <string> class Device{ private: const std::string resource; public: Device(const std::string& res):resource(res){ std::cout << "\nINIT: " << resource << ".\n"; } void work() const { std::cout << "WORKING: " << resource << std::endl; } ~Device(){ std::cout << "SHUT DOWN: "<< resource << ".\n\n"; } }; int main(){ Device resGuard1{"DEVICE 1"}; resGuard1.work(); { Device resGuard2{"DEVICE 2"}; resGuard2.work(); } resGuard1.work(); }

 

Initialise the resource in the constructor and release it in the destructor. First, you can not forget to initialise the object, and secont, the compiler takes care of the release of the resource. The output of both programs is equivalent:

lifecycle

 

You can find more information to RAII in my previous post: C++ Core Guidelines: When RAII breaks.

More Myths

I'm sure, this is not the end of the fight and you know more non-rules and myths about C++. Please write a letter to This email address is being protected from spambots. You need JavaScript enabled to view it.. Describe the myth and present if possible your solution. I try to make a post out of your content and add - if you like it - your name. I'm totally curious about your ideas.

What's next

Only one rule to non-rules and myths is left in the C++ core guidelines. I hope for you input.

 

Thanks a lot to my Patreon Supporters: Paul Baxter,  Meeting C++, Matt Braun, Avi Lachmish, 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, and Broeserl.

 

Thanks in particular to:
 TakeUpCode 450 60
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 2130

All 2894995

Currently are 231 guests and no members online

Kubik-Rubik Joomla! Extensions

Latest comments