C++ Core Guidelines: The Philosophy
Today, I will dig deeper into the C++ Core Guidelines. I wrote about the Introduction section in my last post about the C++ Core Guidelines. 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 consist of 350 rules. They a grouped in the following sections.
- In: Introduction
- P: Philosophy
- I: Interfaces
- F: Functions
- C: Classes and class hierarchies
- Enum: Enumerations
- R: Resource management
- ES: Expressions and statements
- E: Error handling
- Con: Constants and immutability
- T: Templates and generic programming
- CP: Concurrency
- SL: The Standard library
- SF: Source files
- CPL: C-style programming
- Pro: Profiles
- GSL: Guideline support library
- FAQ: Answers to frequently asked questions
Let’s look at each of the 13 philosophy rules. They should provide the rationale for all 350 rules.
Philosophy
The following 13 rules cannot be checked thoroughly. Here are they.
- P.1: Express ideas directly in code
- P.2: Write in ISO Standard C++
- P.3: Express intent
- P.4: Ideally, a program should be statically type-safe
- P.5: Prefer compile-time checking to run-time checking
- P.6: What cannot be checked at compile-time should be checkable at run time
- P.7: Catch run-time errors early
- P.8: Don’t leak any resources
- P.9: Don’t waste time or space
- P.10: Prefer immutable data to mutable data
- P.11: Encapsulate messy constructs, rather than spreading through the code
- P.12: Use supporting tools as appropriate
- P.13: Use support libraries as appropriate
I will provide examples from the C++ Core Guidelines for each rule if possible.
Express ideas directly in code
A method should express its intent. Have a look here.
Modernes C++ Mentoring
Do you want to stay informed: Subscribe.
class Date { // ... public: Month month() const; // do int month(); // don't // ... };
Neither does the second method month express that it will not change the instance nor 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 is 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). On the contrary, the elements in expression (2) will be modified. The algorithm for_each is executed with the 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 impossible 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
- minimize 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
- minimize narrowing conversions (narrowing conversion is an implicit conversion including the loss of data accuracy; for example, a double becomes an int implicitly)
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 powerful 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 readable error message. I 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 wrong with the example? The call (1) does not pass the number of elements. (2) makes passing the wrong number of elements possible, and (3) passes the ownership and the size separately. By bypassing a reference or a view (part of the gsl) you can overcome these 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 the 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 a resource’s acquisition and release 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 pointers and locks are based on this technique. Here are more details of 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 has more optimization 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 at doing boring and repetitive tasks. That means you should use static analysis, concurrency, and testing tools to automate these verifying steps.
Use support libraries as appropriate
That is quite easy to explain. It would be best to go for well-designed, well-documented, and well-supported libraries. Therefore, you will get a well-tested and nearly error-free library and highly optimized algorithms from 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 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, Jozo Leko, John Breland, Venkat Nandam, Jose Francisco, Douglas Tinkham, Kuchlong Kuchlong, Robert Blanch, Truels Wissneth, 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, Stephen Kelley, Kyle Dean, Tusar Palauri, Juan Dent, George Liao, Daniel Ceperley, Jon T Hess, Stephen Totten, Wolfgang Fütterer, Matthias Grün, Phillip Diekmann, Ben Atakora, Ann Shatoff, Rob North, Bhavith C Achar, Marco Parri Empoli, Philipp Lenk, Charles-Jianye Chen, Keith Jeffery,and Matt Godbolt.
Thanks, in particular, to Jon Hess, Lakshman, Christian Wittenhorst, Sherhy Pyton, Dendi Suhubdy, Sudhakar Belagurusamy, Richard Sargeant, Rusty Fleming, John Nebel, Mipko, Alicja Kaminska, Slavko Radman, and David Poole.
My special thanks to Embarcadero | |
My special thanks to PVS-Studio | |
My special thanks to Tipi.build | |
My special thanks to Take Up Code | |
My special thanks to SHAVEDYAKS |
Seminars
I’m happy to give online seminars or face-to-face seminars worldwide. Please call me if you have any questions.
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++
- Clean Code with Modern C++
- C++20
Online Seminars (German)
- Embedded
Programmierung mit modernem C++ (24. Sep. 2024 bis 26.
Sep. 2024)
Contact Me
- Mobil: +49 176 5506 5086
- Mail: schulung@ModernesCpp.de
- German Seminar Page: www.ModernesCpp.de
- Mentoring Page: www.ModernesCpp.org
Modernes C++ Mentoring,
Leave a Reply
Want to join the discussion?Feel free to contribute!