Going from C to C++

Instead of “typedef” use “using”

Show More

    From “https://www.nextptr.com/tutorial/ta1193988140/how-cplusplus-using-or-aliasdeclaration-is-better-than-typedef” :

    
    
    1
    2
    3
    4
    5
    <span class="com">// create an alias of reference to int[4];</span>
    <span class="kwd">using</span> <span class="typ">IntArray4</span> <span class="pun">=</span> <span class="kwd">int</span><span class="pun">(&amp;)[</span><span class="lit">4</span><span class="pun">];</span>

    <span class="com">// or with typedef</span>
    <span class="com">// typedef int(&amp;IntArray4)[4];</span>

    When it comes to defining simpler type aliases, choosing between typedef and alias-declaration could be a matter of personal choice. However, while defining the more complex template aliases, function-pointer aliases, and array reference aliases, the alias-declaration is a clear winner.

    Instead of “#define” use “const”

    Show More

    From : https://root.cern.ch/TaligentDocs/TaligentOnline/DocumentRoot/1.0/Docs/books/WM/WM_92.html

    Never use #define symbolic constants Instead, use the C++ const storage class. As with #define symbols, const declarations are evaluated at compile time (for types and expressions that qualify as compile-time constants). Unlike #define symbols, they follow the C scope rules and have types associated with them. You can also use enum to prevent a host of problems. For example:

    #define kGreen 1 // Bad const int kGreen = 1; // Better enum Color {kRed, kGreen, kBlue} // Best

    If you accidentally redefine a name with a #define, the compiler silently changes the meaning of your program. With const or enum you get an error message. Even better, with enum you can put the identifiers in the scope of a class. As an added bonus, each enumeration is treated as a separate type for purposes of type checking (much like the way scalars are handled in Pascal) and for purposes of overloading.

    Unlike in ANSI C, objects in C++ that are declared const and initialized with compile-time expressions are themselves compile-time constants (but only if they are of integral or enumeration type). Thus, they can be used as case labels or in compile-time expressions.

    Use auto whenever possible

    https://subscription.packtpub.com/book/application_development/9781786465184/1/ch01lvl1sec5/using-auto-whenever-possible

    Use C++ cast instead of C casting

    int f = (int) test; vs static_cast<int>(test) –  See http://www.cplusplus.com/doc/oldtutorial/typecasting/

    Use std::string instead of char array

    1
    std::string

     instead of character arrays

    Use the Standard Template Library containers

    Migrate from hand-crafted containers to C++ Standard Template Library containers

    Use Nullptr instead of Null

    Use Namespaces

    Show More

    Here is a blog about why ;https://cppdepend.com/blog/?p=79

    
    
    1
    2
    3
    4
    <var>namespace</var> myNamespace
    {
      <var>int</var> a, b;
    }

    Use reference instead of pointer for parameters to functions

    Reference can’t be NULL, so you don’t be accident de-reference a NULL pointer.

     

    Use ++i instead of i++

    You should prefer pre-increment operator (++iter) to post-increment operator (iter++) if you are not going to use the old value. Avoids copy of value

    Scoped Enumerations

    From : https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#example-149 – To minimize surprises: traditional enums convert to int too readily.

    
    
    1
    2
    3
    4
    void Print_color(int color);

    enum Web_color { red = 0xFF0000, green = 0x00FF00, blue = 0x0000FF };
    enum Product_info { red = 0, purple = 1, blue = 2 };

    Use Enum Class

    
    
    1
    2
     <span class="kwd">enum</span> <span class="kwd">class</span> <span class="typ">Color</span> <span class="pun">{</span><span class="pln"> red</span><span class="pun">,</span><span class="pln"> green</span><span class="pun">,</span><span class="pln"> blue </span><span class="pun">};</span> <span class="com">// enum class</span>
     <span class="kwd">enum</span> <span class="typ">Animal</span> <span class="pun">{</span><span class="pln"> dog</span><span class="pun">,</span><span class="pln"> cat</span><span class="pun">,</span><span class="pln"> bird</span><span class="pun">,</span><span class="pln"> human </span><span class="pun">};</span> <span class="com">// plain enum </span>

    What is the difference between two?

    • 1
      enum class

      es – enumerator names are local to the enum and their values do not implicitly convert to other types (like another 

      1
      enum

       or 

      1
      int

      )

    • Plain 
      1
      enum

      s – where enumerator names are in the same scope as the enum and their values implicitly convert to integers and other types

     

    Prefer compile-time checking to run-time checking

    Reason

    Code clarity and performance. You don’t need to write error handlers for errors caught at compile time.

    
    
    1
    2
    // Int is an alias used for integers
    static_assert(sizeof(Int) &gt;= 4);    // do: compile-time check

     

    P.8: Don’t leak any resources

    Prefer RAII (Clean up in the destructor)

    I.4: Make interfaces precisely and strongly typed

    Reason

    Types are the simplest and best documentation, improve legibility due to their well-defined meaning, and are checked at compile time. Also, precisely typed code is often optimized better.

    Example bad:

    
    
    1
    set_settings(true, false, 42); // what do the numbers specify?

    Example Good:’

    
    
    1
    2
    3
    4
    s.enabled     = true;
    s.displayMode = false;
    s.frequency   = 100;
    set_settings(s);

    I.23: Keep the number of function arguments low

    Show More
    Reason

    Having many arguments opens opportunities for confusion. Passing lots of arguments is often costly compared to alternatives.

    F.2: A function should perform a single logical operation

    Show More
    Reason

    A function that performs a single operation is simpler to understand, test, and reuse.

    F.3: Keep functions short and simple

    Show More
    Reason

    Large functions are hard to read, more likely to contain complex code, and more likely to have variables in larger than minimal scopes. Functions with complex control structures are more likely to be long and more likely to hide logical errors

    F.4: If a function may have to be evaluated at compile time, declare it constexpr

    Show More

    Reason

    constexpr is needed to tell the compiler to allow compile-time evaluation.

    Example

    The (in)famous factorial:

    
    
    1
    2
    3
    4
    5
    6
    7
    8
    constexpr int fac(int n)
    {
        constexpr int max_exp = 17;      // constexpr enables max_exp to be used in Expects
        Expects(0 &lt;= n &amp;&amp; n &lt; max_exp);  // prevent silliness and overflow
        int x = 1;
        for (int i = 2; i &lt;= n; ++i) x *= i;
        return x;
    }

     

    F.5: If a function is very small and time-critical, declare it inline

    Show More
    Reason

    Some optimizers are good at inlining without hints from the programmer, but don’t rely on it. Measure! Over the last 40 years or so, we have been promised compilers that can inline better than humans without hints from humans. We are still waiting. Specifying inline encourages the compiler to do a better job.

    F.16: For “in” parameters, pass cheaply-copied types by value and others by reference to 

    1
    const

    Show More
    Reason

    Both let the caller know that a function will not modify the argument, and both allow initialization by rvalues.

    What is “cheap to copy” depends on the machine architecture, but two or three words (doubles, pointers, references) are usually best passed by value. When copying is cheap, nothing beats the simplicity and safety of copying, and for small objects (up to two or three words) it is also faster than passing by reference because it does not require an extra indirection to access from the function.

    Example
    
    
    1
    2
    3
    4
    5
    6
    7
    void f1(const string&amp; s);  // OK: pass by reference to const; always cheap

    void f2(string s);         // bad: potentially expensive

    void f3(int x);            // OK: Unbeatable

    void f4(const int&amp; x);     // bad: overhead on access in f4()

     

    F.20: For “out” output values, prefer return values to output parameters

    Show More
    Reason

    A return value is self-documenting, whereas a 

    1
    &amp;

     could be either in-out or out-only and is liable to be misused.

    This includes large objects like standard containers that use implicit move operations for performance and to avoid explicit memory management.

    If you have multiple values to return, use a tuple or similar multi-member type.

     

    F.23: Use a 

    1
    not_null&lt;T&gt;

     to indicate that “null” is not a valid value

    Show More
    Reason

    Clarity. A function with a 

    1
    not_null&lt;T&gt;

     parameter makes it clear that the caller of the function is responsible for any 

    1
    nullptr

     checks that may be necessary. Similarly, a function with a return value of 

    1
    not_null&lt;T&gt;

     makes it clear that the caller of the function does not need to check for 

    1
    nullptr

    .

    Example
    1
    not_null&lt;T*&gt;

     makes it obvious to a reader (human or machine) that a test for 

    1
    nullptr

     is not necessary before dereference. Additionally, when debugging, 

    1
    owner&lt;T*&gt;

     and 

    1
    not_null&lt;T&gt;

     can be instrumented to check for correctness.

    Consider:

    
    
    1
    int length(Record* p);

    When I call 

    1
    length(p)

     should I check if 

    1
    p

     is 

    1
    nullptr

     first? Should the implementation of 

    1
    length()

     check if 

    1
    p

     is 

    1
    nullptr

    ?

    
    
    1
    2
    3
    4
    5
    // it is the caller's job to make sure p != nullptr
    int length(not_null&lt;Record*&gt; p);

    // the implementor of length() must assume that p == nullptr is possible
    int length(Record* p);

    F.60: Prefer 

    1
    T*

     over 

    1
    T&amp;

     when “no argument” is a valid option

    Show More
    Reason

    A pointer (

    1
    T*

    ) can be a 

    1
    nullptr

     and a reference (

    1
    T&amp;

    ) cannot, there is no valid “null reference”. Sometimes having 

    1
    nullptr

     as an alternative to indicated “no object” is useful, but if it is not, a reference is notationally simpler and might yield better code.

    Example
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    string zstring_to_string(zstring p) // zstring is a char*; that is a C-style string
    {
        if (!p) return string{};    // p might be nullptr; remember to check
        return string{p};
    }

    void print(const vector&lt;int&gt;&amp; r)
    {
        // r refers to a vector&lt;int&gt;; no check needed
    }

    F.51: Where there is a choice, prefer default arguments over overloading

    Show More
    Reason

    Default arguments simply provide alternative interfaces to a single implementation. There is no guarantee that a set of overloaded functions all implement the same semantics. The use of default arguments can avoid code replication.

    Note

    There is a choice between using default argument and overloading when the alternatives are from a set of arguments of the same types. For example:

    
    
    1
    void print(const string&amp; s, format f = {});

    as opposed to

    
    
    1
    2
    void print(const string&amp; s);  // use default format
    void print(const string&amp; s, format f);

     

    C.1: Organize related data into structures (

    1
    struct

    s or 

    1
    class

    es)

    Show More
    Reason

    Ease of comprehension. If data is related (for fundamental reasons), that fact should be reflected in code.

    Example
    
    
    1
    2
    void draw(int x, int y, int x2, int y2);  // BAD: unnecessary implicit relationships
    void draw(Point from, Point to);          // better

    C.20: If you can avoid defining default operations, do

    Show More
    Reason

    It’s the simplest and gives the cleanest semantics.

    C.41: A constructor should create a fully initialized object

    Show More
    Reason

    A constructor establishes the invariant for a class. A user of a class should be able to assume that a constructed object is usable.

    Example, bad
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    class X1 {
        FILE* f;   // call init() before any other function
        // ...
    public:
        X1() {}
        void init();   // initialize f
        void read();   // read from f
        // ...
    };

    void f()
    {
        X1 file;
        file.read();   // crash or bad read!
        // ...
        file.init();   // too late
        // ...
    }

    Compilers do not read comments.

     

    C.35: A base class destructor should be either public and virtual, or protected and non-virtual

    Show More
    Reason

    To prevent undefined behavior. If the destructor is public, then calling code can attempt to destroy a derived class object through a base class pointer, and the result is undefined if the base class’s destructor is non-virtual. If the destructor is protected, then calling code cannot destroy through a base class pointer and the destructor does not need to be virtual; it does need to be protected, not private, so that derived destructors can invoke it. In general, the writer of a base class does not know the appropriate action to be done upon destruction.