Posts C++ some selected core guidelines
Post
Cancel

C++ some selected core guidelines

The core guidelines have about 496 items. Effective modern C++ has 42, which does not include the other effective books. C++ coding standards has 101 items. Just from these items we have over 600 best practices. How many unique ones? How many important ones? Which ones can be detected by tools like static analysis? Let’s take a look at at least some of the ones that are considered “more” important.

C.45

Don’t define a default constructor that only initializes data members; use in-class member initializers instead.

C.48

Prefer in-class initializers to member initializers in constructors for constant initializers.

1
2
3
4
5
6
7
8
9
10
class Simple {
public:
    Simple(): a(1), b(2), c(3) {}
    Simple(int aa, int bb, int cc=-1): a(aa), b(bb), c(cc) {}
    Simple(int aa) {a=aa;b=0;c=0;}
private:
    int a;
    int b;
    int c;
};

Instead, strive for:

1
2
3
4
5
6
7
8
9
10
class Simple {
public:
    Simple() {}
    Simple(int aa, int bb, int cc): a(aa), b(bb), c(cc) {}
    Simple(int aa): a(aa) {}
private:
    int a = -1;
    int b = -1;
    int c = -1;
};

We can actually reduce chance of inconsistency even further by specifying a default constructor:

1
2
3
4
5
6
7
8
9
10
class Simple {
public:
    Simple() = default;
    Simple(int aa, int bb, int cc): a(aa), b(bb), c(cc) {}
    Simple(int aa): a(aa) {}
private:
    int a = -1;
    int b = -1;
    int c = -1;
};

F.51

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

1
2
3
4
5
6
class Widget {
public:
    double Offset(double a, double b, double ff);
    double Offset(double a, double b);
...
};

Instead strive for:

1
2
3
4
5
6
class Widget {
public:
    double Offset(double a, double b, double ff=1.0);
...
};

C.47

Define and initialize member variables in the order of member declaration.

For example:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Example {
public:
    Example(int i): a(++i), b(++i), x(++i) {}
private:
    int a;
    int x;
    int b;
};

int main() {
    Example e(0);
    return 0;
}

What will the values of a, b and x be? It will not actually be 0,1,2 despite what maybe seems logical. The variables will be initialized in the order that they are present in member declaration. So, if someone swaps around your member variables, this may actually break your code.

I.23

Keep the number of function arguments low.

What is a lot of arguments? Well, it really depends. But, usually anything above 5 may warrant thinking about the design of the function.

For example:

1
2
int area(int x1, int y1, int x2, int y2);
int a = area(1, 1, 11, 21);

Looks kind of dodgy and we might forget in what order the person making the area function defined the arguments. Instead we can use an abstraction, for example:

1
2
int area(Point p1, Point p2);
int a = area({1, 1},{11, 21});

And now, the intention is much clearer.

ES.50

Don’t cast away const.

Pretty self explanatory from the guidelines examples.

Instead we can use a pointer for example, to something we can’t make const, but the pointer itself can be const.

Or we can set those values as mutable.

I.11

Never transfer ownership by a raw pointer (T*)

Don’t do this:

1
2
3
4
5
Policy* SetupAndPrice(args) {
    Policy *p = new Policy{...};
    ...
    return p;
}

Whatever calls this needs to know it needs to clean this up, or else it will leak.

Alternatively, just return by value. Create it on the stack, and return it. Unless it is indeed extremely expensive to copy.

Or, we can take it by non-const reference and change it.

Perhaps use a smart pointer. This is probably the best choice.

There is also an option of using owner. If we are using somebodies API and we need to declare that it returns Policy * we can say it returns an owner of Policy *. This sets up extra checkers that can tell us if we are miss-managing resources.

F.21

To return multiple “out” values, prefer returning a struct or tuple.

1
2
3
4
5
6
7
8
9
10
int foo(int inValue, int& outValue) {
    outValue = inValue * 2;
    return inValue * 3;
}

int main() {
    int number = 4;
    int answer = foo(5, number);
    return 0;
}

Instead, prefer returning a struct:

1
2
3
4
5
6
7
8
struct twoNumbers {
    int value1;
    int value2;
}

twoNumbers fooStruct(int inValue) {
    return twoNumbers{inValue * 2, inValue * 3};
}

What if we want to return a bool and a value, where the bool essentially indicates whether the value is usable or not? For example, this pattern is often seen in Go error checking:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func doStuff() (result string, err error) {
    a, err := doA()
    if err != nil {
        return
    }
    b, err := doB(a)
    if err != nil {
        return
    }
    result, err = doC(b)
    if err != nil {
        return
    }
    return
}

In C++ we can use std::optional for this. If two things are an object and a bool about whether or not that object is usable, we can hence consider optional<T>. Casting an optional to bool returns true if it contains a value, false otherwise.

Our example with the twoNumber struct however does have an obvious flaw. The struct itself is not of much use. This would be different if the struct was say customer or something meaningful, but do we really need a struct for two numbers?

In this case we can just use a tuple:

1
2
3
4
5
6
7
8
9
std::tuple<int, int> fooTuple(int inValue) {
    return std::make_tuple(inValue * 2, inValue * 3);
}

int main() {
    int number, answer;
    std::tie(answer, number) = fooTuple(9);
    return 0;
}

In C++17 life becomes even easier as we can do tuple unpacking as follows:

1
2
3
4
int main() {
    auto[answer, number] = fooTuple(9);
    return 0;
}

Enum.3

Prefer class enums over “plain” enums.

This also allows us to have same names in enums, so avoids having the previously awkward enum naming conventions.

1
2
enum class Error {OK, FileNotFound, OutOfMemory};
enum class Ratings{Terrible, OK, Terrific;}

These are used with their full names:

1
2
3
Error result = Error::OK;
Ratings stars = Ratings::OK;
int r = static_cast<int>(result);
This post is licensed under CC BY 4.0 by the author.

Recent Update

    Contents