naming standards clean code 24 728

C++ Core Guidelines: Rules for Expressions and Statements

There are many rules in the C++ Core Guidelines dealing with expressions and statements. To be precise, there are more than 50 rules about declarations, expressions, statements, and arithmetic expressions.

naming standards clean code 24 728 

 

I forget to mention two rules that are just called general. Here we are.

ES.1: Prefer the standard library to other libraries and to “handcrafted code”

There is no reason to write a raw loop to sum up a vector of doubles:

int max = v.size();             // bad: verbose, purpose unstated
double sum = 0.0;
for (int i = 0; i < max; ++i)
    sum = sum + v[i];

 

You should just use the std::accumulate algorithm from the STL.

auto sum = std::accumulate(begin(a), end(a), 0.0);   // good

 

This rule reminds me to a sentence from Sean Parent at CppCon 2013: “If you want to improve the code quality in your organization, replace all your coding guidelines with one goal: No raw loops!”

 

Rainer D 6 P2 500x500Modernes C++ Mentoring

  • "Fundamentals for C++ Professionals" (open)
  • "Design Patterns and Architectural Patterns with C++" (open)
  • "C++20: Get the Details" (open)
  • "Concurrency with Modern C++" (open)
  • "Generic Programming (Templates) with C++": October 2024
  • "Embedded Programming with Modern C++": October 2024
  • "Clean Code: Best Practices for Modern C++": March 2025
  • Do you want to stay informed: Subscribe.

     

    Or to say it more directly: If you write a raw loop, you probably don’t know the algorithms of the STL.

    ES.2: Prefer suitable abstractions to direct use of language features

    The next déjà vu. In one of my last C++ seminars, I had a long discussion followed by an even longer analysis of a few quite sophisticated and handmade functions for reading and writing strstreams. The participants had to maintain these functions and had, after one week, no idea what was going on.

    The main obstacle to don’t understanding the functionality was that the functionality was not based on the right abstraction.

    For example, compare the handmade function for reading a std::istream.

    char** read1(istream& is, int maxelem, int maxstring, int* nread)   // bad: verbose and incomplete
    {
        auto res = new char*[maxelem];
        int elemcount = 0;
        while (is && elemcount < maxelem) {
            auto s = new char[maxstring];
            is.read(s, maxstring);
            res[elemcount++] = s;
        }
        nread = &elemcount;
        return res;
    }
    

     

    In contrast, how easy is the following function to consume?

    vector<string> read2(istream& is)   // good
    {
        vector<string> res;
        for (string s; is >> s;)
            res.push_back(s);
        return res;
    }
    

     

    The right abstraction often means you do have not to think about ownership, such as in the function read1. This will not hold for the function read2. The caller of read1 is the owner of result and has to delete it.

    A declaration introduces a name into a scope. To be honest, I’m biased. On the one hand, the following rules are a little bit borrowing for you because they are pretty obvious. On the other hand, I know a lot of codebases that permanently break these rules. For example, I had a discussion with a former Fortran programmer, who stated: Each variable should have exactly three characters.

    Anyway, I will continue presenting the rules because good names a probably the key to making code readable, understandable, maintainable, and extensible, …

    Here are the first six rules.

    ES.5: Keep scopes small

    If a scope is small, you can put it on a screen and get an idea of what is happening. If a scope becomes too big, you should structure your code into functions or objects with methods. Identify logical entities and use self-explanatory names in your refactoring process. Afterward, it is a lot easier to think about your code.

    ES.6: Declare names in for-statement initializers and conditions to limit scope

    Since the first C++ standard, we can declare a variable in a for statement. Since C++17, we can declare variables in an if or a switch statement.

    std::map<int,std::string> myMap;
    
    if (auto result = myMap.insert(value); result.second){  // (1)
        useResult(result.first);  
        // ...
    } 
    else{
        // ...
    } // result is automatically destroyed                 // (2)
    

     

    The variable result (1) is only valid inside the if and else branches of the if statement. result will not pollute the outer scope and will be automatically destroyed (2). This can not be done before C++17. You have to declare the result in the outer scope (3).

     

    std::map<int,std::string> myMap;
    auto result = myMap.insert(value)   // (3)
    if (result.second){  
        useResult(result.first);  
        // ...
    } 
    else{
        // ...
    } 
    

     

    ES.7: Keep common and local names short, and keep uncommon and nonlocal names longer

    This rule sounds strange, but we are already used to it. Giving a variable the name i or j or giving a variable the name T will make the code’s intention immediately clear: i and j are indices, and T is a type parameter of a template.

    template<typename T>    // good
    void print(ostream& os, const vector<T>& v)
    {
        for (int i = 0; i < v.size(); ++i)
            os << v[i] << '\n';
    }
    

     

    There is a meta-rule behind this rule. A name should be self-explanatory. In a short context, you get what the variable means with a glance. This will not automatically hold for more extended contexts; therefore, you should use longer names. 

    ES.8: Avoid similar-looking names

    Can you read this example without any hesitation?

    if (readable(i1 + l1 + ol + o1 + o0 + ol + o1 + I0 + l0)) surprise();
    

     

    Honestly, I often have problems with the number 0 and the significant capital O. Depending on the font used; it looks similar. Two years ago, logging into a server took me quite a while. My automatically generated password had a character O.

    ES.9: Avoid ALL_CAPS names

    If you use, ALL_CAPS macro substitution may kick in because ALL_CAPS is commonly used for macros. The following program snippet may have a little surprise involved.

    // somewhere in some header:
    #define NE !=
    
    // somewhere else in some other header:
    enum Coord { N, NE, NW, S, SE, SW, E, W };
    
    // somewhere third in some poor programmer's .cpp:
    switch (direction) {
    case N:
        // ...
    case NE:
        // ...
    // ...
    }
    

     

    ES.10: Declare one name (only) per declaration

    Let me give you two examples. Did you spot the two issues?

     

    char* p, p2;
    char a = 'a';
    p = &a;
    p2 = a;                              // (1)
    
    int a = 7, b = 9, c, d = 10, e = 3;  // (2)
    

     

    p2 is just a char  (1), and c is not initialized (2).

    With C++17, we got one exception to this rule: structured binding.

    Now, I can write the if statement with an initializer in rule ES.6 even cleaner and more readable.

     

    std::map<int,std::string> myMap;
    
    if (auto [iter, succeeded] = myMap.insert(value); succedded){  // (1)
        useResult(iter);  
        // ...
    } 
    else{
        // ...
    } // iter and succeeded are automatically destroyed            // (2)
    

     

    What’s next?

    Of course, I will continue with the rules regarding declarations in my next post.

     

     

     

     

    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

    Modernes C++ GmbH

    Modernes C++ Mentoring (English)

    Do you want to stay informed about my mentoring programs? Subscribe Here

    Rainer Grimm
    Yalovastraße 20
    72108 Rottenburg

    Mobil: +49 176 5506 5086
    Mail: schulung@ModernesCpp.de
    Mentoring: www.ModernesCpp.org

    Modernes C++ Mentoring,

     

     

    0 replies

    Leave a Reply

    Want to join the discussion?
    Feel free to contribute!

    Leave a Reply

    Your email address will not be published. Required fields are marked *