Instead of “typedef” use “using”
Show More
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">(&)[</span><span class="lit">4</span><span class="pun">];</span>
<span class="com">// or with typedef</span>
<span class="com">// typedef int(&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 MoreUse 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
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?
-
1enum class
es – enumerator names are local to the enum and their values do not implicitly convert to other types (like another
1enumor
1int)
- Plain
1enum
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) >= 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
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 <= n && n < max_exp); // prevent silliness and overflow
int x = 1;
for (int i = 2; i <= n; ++i) x *= i;
return x;
}
F.5: If a function is very small and time-critical, declare it inline
Show MoreF.16: For “in” parameters, pass cheaply-copied types by value and others by reference to
1
const
1 | const |
Show More
1
2
3
4
5
6
7 void f1(const string& 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& 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 | & |
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<T>
1 | not_null<T> |
to indicate that “null” is not a valid value
Show More
Reason
Clarity. A function with a
1 | not_null<T> |
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<T> |
makes it clear that the caller of the function does not need to check for
1 | nullptr |
.
Example
1 | not_null<T*> |
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<T*> |
and
1 | not_null<T> |
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<Record*> p);
// the implementor of length() must assume that p == nullptr is possible
int length(Record* p);
F.60: Prefer
1
T*
1 | T* |
over
1 | T& |
when “no argument” is a valid option
Show More
Reason
A pointer (
1 | T* |
) can be a
1 | nullptr |
and a reference (
1 | T& |
) 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<int>& r)
{
// r refers to a vector<int>; 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& s, format f = {});
as opposed to
1
2 void print(const string& s); // use default format
void print(const string& s, format f);
C.1: Organize related data into structures (
1
struct
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.
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.45: Don’t define a default constructor that only initializes data members; use in-class member initializers instead
Show More
Reason
Using in-class member initializers lets the compiler generate the function for you. The compiler-generated function can be more efficient.
Example, bad
1
2
3
4
5
6
7 class X1 { // BAD: doesn't use member initializers
string s;
int i;
public:
X1() :s{"default"}, i{1} { }
// ...
};
Example
1
2
3
4
5
6
7 class X2 {
string s = "default";
int i = 1;
public:
// use compiler-generated default constructor
// ...
};
C.45: Don’t define a default constructor that only initializes data members; use in-class member initializers instead
Show More
Reason
Using in-class member initializers lets the compiler generate the function for you. The compiler-generated function can be more efficient.
Example, bad
1
2
3
4
5
6
7 class X1 { // BAD: doesn't use member initializers
string s;
int i;
public:
X1() :s{"default"}, i{1} { }
// ...
};
Example
1
2
3
4
5
6
7 class X2 {
string s = "default";
int i = 1;
public:
// use compiler-generated default constructor
// ...
};
C.48: Prefer in-class initializers to member initializers in constructors for constant initializers
Reason
Makes it explicit that the same value is expected to be used in all constructors. Avoids repetition. Avoids maintenance problems. It leads to the shortest and most efficient code.
Example, bad
1
2
3
4
5
6
7
8
9 class X { // BAD
int i;
string s;
int j;
public:
X() :i{666}, s{"qqq"} { } // j is uninitialized
X(int ii) :i{ii} {} // s is "" and j is uninitialized
// ...
};
How would a maintainer know whether
1 | j |
was deliberately uninitialized (probably a bad idea anyway) and whether it was intentional to give
1 | s |
the default value
1 | "" |
in one case and
1 | qqq |
in another (almost certainly a bug)? The problem with
1 | j |
(forgetting to initialize a member) often happens when a new member is added to an existing class.
Example
1
2
3
4
5
6
7
8
9 class X2 {
int i {666};
string s {"qqq"};
int j {0};
public:
X2() = default; // all members are initialized to their defaults
X2(int ii) :i{ii} {} // s and j initialized to their defaults
// ...
};
[/bg_collapse]
Show More
C.49: Prefer initialization to assignment in constructors
Show More
Reason
An initialization explicitly states that initialization, rather than assignment, is done and can be more elegant and efficient. Prevents “use before set” errors.
Example, good
1
2
3
4
5
6 class A { // Good
string s1;
public:
A(czstring p) : s1{p} { } // GOOD: directly construct (and the C-string is explicitly named)
// ...
};
Example, bad
1
2
3
4
5
6
7
8
9
10
11
12
13 class B { // BAD
string s1;
public:
B(const char* p) { s1 = p; } // BAD: default constructor followed by assignment
// ...
};
class C { // UGLY, aka very bad
int* p;
public:
C() { cout << *p; p = new int{10}; } // accidental use before initialized
// ...
};
Show More
C.51: Use delegating constructors to represent common actions for all constructors of a class
Reason
To avoid repetition and accidental differences.
Example, bad
1
2
3
4
5
6
7
8
9
10
11
12
13
14 class Date { // BAD: repetitive
int d;
Month m;
int y;
public:
Date(int dd, Month mm, year yy)
:d{dd}, m{mm}, y{yy}
{ if (!valid(d, m, y)) throw Bad_date{}; }
Date(int dd, Month mm)
:d{dd}, m{mm} y{current_year()}
{ if (!valid(d, m, y)) throw Bad_date{}; }
// ...
};
The common action gets tedious to write and might accidentally not be common.
Example
1
2
3
4
5
6
7
8
9
10
11
12
13 class Date2 {
int d;
Month m;
int y;
public:
Date2(int dd, Month mm, year yy)
:d{dd}, m{mm}, y{yy}
{ if (!valid(d, m, y)) throw Bad_date{}; }
Date2(int dd, Month mm)
:Date2{dd, mm, current_year()} {}
// ...
};
Show More