C++ Core Guidelines: The Philosophy

Today, I will dig a little bit deeper into the C++ Core Guidelines. In my last post about the C++ Core Guidelines I wrote about the Introduction section. Today I write about the section that is "primarily for humans". This is the most general section and is called Philosophy. The rules are so general that you can apply them to each programming language.

 

Only to remind you. The C++ Core Guidelines consists of 350 rules. They a grouped in the following sections.

Let's look at each of the 13 philosophy rules. They should provide the rationale for all 350 rules.

Philosophy

 800px Head Platon Glyptothek Munich 548

The following 13 rules cannot be checked completely. Here are they.

If possible, I will provide examples from the C++ Core Guidelines to each of the rules.

Express ideas directly in code

A method should express it's intent. Have a look here.

class Date {
    // ...
public:
    Month month() const;  // do    
    int month();          // don't
    // ...
};

 

Neither does the second method month express that is will not change the instance nor does it return a return a Month object.

The same reasoning often holds for explicit loops versus algorithms of the Standard Template Library (STL).

int index = -1;                    // bad
for (int i = 0; i < v.size(); ++i) {
    if (v[i] == val) {
      index = i;
      break;
    }
}

auto p = find(begin(v), end(v), val);  // better

 

The meta rule is obvious. You should know and use the algorithms of the STL.

Write in ISO Standard C++

This rule reads quite simple and has a straightforward enforcement statement: "Use an up-to-date C++ compiler (currently C++11 or C++14) with a set of options that do not accept extensions."

Express intent

Your code should express its intent. What can we deduce from the three explicit and implicit loops?

for (const auto& v: vec) { ... }       (1)
for (auto& v: vec){ ... }              (2)
for_each(par, vec, [](auto v){ ... }); (3)

 

The elements of the container vec will not be modified in (1). In contrary, the elements expression (2) will be modified. The algorithm for_each is executed with parallel execution policy (par). That means the order of iteration does not matter.

Ideally, a program should be statically type safe

You should strive for statically type safe programs. Of course, that is not possible because there are problem areas in C++. The C++ Core Guidelines name the problem areas and possible solutions.

  • use std::variant (new with C++17) instead of unions
  • minimise the use of casts; use templates if possible
  • use gsl::span against array decay (if you pass an array to a function it will implicitly decay to a pointer) and range errors
  • minimise narrowing conversions (narrowing conversion is an implicit conversion including the loss of data accuracy; for example, a double becomes implicitly an int)

gsl stands for Guideline support library GSL. GSL is a small library to support the set of guidelines from the C++ Core Guidelines. I will write about the GSL in an upcoming post.

Prefer compile-time checking to run-time checking

All that can be done at compile time must not be done at run time. Since C++11 we have the function static_assert and the type-traits library. static_assert will check a predicate such as static_assert(sizeof(int) >= 4) at compile time. Thanks to the type-traits library, we can state mighty conditions about a type T at compile time: static_assert(std::is_integral<T>::value). Of course, if the check fails at compile time, the compilation will fail with a readably error message. already wrote about static_assert.

What cannot be checked at compile time should be checkable at run time

This rule talks about hard-to-detect errors that should be avoided. The examples are about dynamically allocated arrays.

extern void f(int* p);
extern void f2(int* p, int n);
extern void f3(unique_ptr<int[]> uniq, int n);

f(new int[n]);                  (1)
f2(new int[n], m);              (2)
f3(make_unique<int[]>(n), m);   (3)

 

What's bad about the examples? The call (1) does not pass the number of elements. (2) makes it possible to pass the wrong number of elements and (3) passes the ownership and the size separately. By passing a reference or a vewi (part of the gsl) you can overcome this issues.

Catch run-time errors early

Here are the enforcements for this rule:

  • Look at pointers and arrays: Do range-checking early and not repeatedly
  • Look at conversions: Eliminate or mark narrowing conversions
  • Look for unchecked values coming from input
  • Look for structured data (objects of classes with invariants) being converted into strings

Don't leak any resource

Leaking resources are in particular critical for long-running programs. Resources may be memory but also file handles or sockets. The idiomatic way to solve this issue is RAII. The idea of the RAII idiom is quite simple. You bind the acquisition and the release of a resource to the lifetime of a local object. Therefore, the resource will automatically be initialized in the constructor and released in the destructor. The acronym RAII stands for Resource Acquisition Is Initialization. Smart pointer and locks are based on this technique. Here are more details to RAII.

Don't waste time or space

The reason for this rule is quite promising: "This is C++". One example of the rule is a riddle.

What's wasted here?

void lower(string s)
{
    for (int i = 0; i < strlen(s); ++i) s[i] = tolower(s[i]);
}

 

Prefer immutable data to mutable data

There are a lot of reasons that speak for immutable data:

  • It's easier to reason about constants than about variables.
  • Constant have more optimisation potential.
  • Constants are free of data races.

Encapsulate messy constructs, rather than spreading through the code

Messy code is prone to bugs and harder to write. Therefore, you should encapsulate the low-level code in a function or a method and put a good interface around it.

Use supporting tools as appropriate

Computers are better than humans in doing the boring and repetitive tasks. That means you should use static analysis tools, concurrency tools, and testing tools to automate this verifying steps.

Use support libraries as appropriate

That is quite easy to explain. You should go for well-designed, well-documented, and well-supported libraries. Therefore, you will get a well-tested and a nearly error-free library and highly optimised algorithms from the domain experts. Two outstanding examples are the C++ standard library and the Guidelines Support Library.

What's next?

An interface is a contract between the service provider and the service user. There are 20 rules about interfaces in the C++ Core Guidelines. In the next post, I will have a closer look at interfaces.

 

 

 

Thanks a lot to my Patreon Supporter: Eric Pederson.

 

 

title page smalltitle page small Go to Leanpub/cpplibrary "What every professional C++ programmer should know about the C++ standard library".   Get your e-book. Support my blog.

 

Add comment


My Newest E-Books

Latest comments

Subscribe to the newsletter (+ pdf bundle)

Blog archive

Source Code

Visitors

Today 378

All 496951

Currently are 186 guests and no members online