Why this excerpt? Because when I was learning C++ code, I found that its functional advantages were too powerful and that there were no strong restrictions on writing code, and C++ has actually carried a lot of old-world atmosphere after decades of iteration, which has led to different habits of people from different backgrounds writing C++ code in different times, so I wanted to learn as much as possible from how good teams write C++ code, so I wrote this one excerpt.

Inline functions

Define a function as inline only if it has 10 lines or less.

Abuse of inlining will cause the program to become slower. Inline may increase or decrease the amount of target code, depending on the size of the inline function. Inline is very short A small store-and-fetch function will usually reduce the code size, but inlining a fairly large function will dramatically increase the code size.

Also do not inline functions that contain loops or switch statements, dummy functions, and recursive functions, as these functions are usually not well inlined.

Try not to use fully naked global functions

Placing non-member functions in the namespace avoids polluting the global scope. This is actually quite important, as it reduces unnecessary coupling and link-time dependencies.

Be careful where you declare variables

It is generally advisable to declare variables in the smallest possible scope, as close to the first use as possible. This makes it easier for the code viewer to locate where the variable is declared and to understand the type and initial value of the variable.

However, there are some exceptions.

1
2
3
4
5
6
// Inefficient implementation
for (int i = 0; i < 1000000; ++i) {
    Foo f;
    // Constructors and destructors are called 1000000 times each!
    f.DoSomething(i);
}

If the variable is an object, its constructor is called every time it enters scope, and its destructor is called every time it exits scope. It is much more efficient to declare such variables outside of the loop scope.

1
2
3
4
5
// Constructors and destructors are called only 1 time
Foo f;
for (int i = 0; i < 1000000; ++i) {
    f.DoSomething(i);
}

Prohibit the use of static or global variables of class type

The order of constructors, destructors, and initialization of global variables of class types is indeterminate in C++, even changing with build changes, leading to hard-to-detect bugs, and the destructor order is also indeterminate.

Initialization of classes

If member variables are defined in a class, you must provide an initialization function or define a constructor for each class in the class. If a constructor is not declared, the The compiler will generate a default constructor, which may cause some members to be uninitialized or initialized to inappropriate values.

This is unlike java or go, which have a default initialization value, but C++ does not, so if your class has member variables that are not initialized inside the class, and no other constructor is provided, you must define a default constructor (without arguments). It makes more sense to initialize the internal state of the object to a consistent/valid value.

Copyable and moveable types

If your types require it, make them support copy/move. Otherwise, disable copy and move functions that are implicitly generated.

Copy/move constructors generally have their specific uses, such as making code cleaner and performing better, but they are implicit operations that can often be confusing and ignored. If your class does not need copy/move operations, disable them explicitly by = delete or other means.

Overloading operators

Do not overload operators except in a few specific circumstances.

To make the code look more intuitive, classes behave like built-in types (e.g. int) can use operators like + and /. But this implicit operation tends to confuse you by making you think that a time-consuming operation is as light as manipulating a built-in type. And it’s much harder to locate the call point of an overloaded operator; it’s obviously easier to find Equals() than the corresponding == call point.

So, in general, do not overload operators. In particular, the assignment operator (operator=) is weird and should be avoided. If necessary, you can define functions like Equals(), CopyFrom(), etc.

Not using C++ exceptions

When I was writing java projects, I used to use exceptions to handle errors, and then wrap a big try catch in the outer layer to catch them, which meant that you had to check all the call points to use the exceptions, otherwise you’d just watch the exceptions run happily all the way up and eventually break the whole program.

Exceptions also tend to disrupt the flow of execution and are difficult to determine, and functions may return in places you didn’t expect. And abusing exceptions can encourage developers to catch “pseudo-exceptions” that are out of place or already unrecoverable.

Instead, we can use error codes, assertions, and other actions to keep our programs running properly.

Try to use C++ type conversions

Many people prefer to use C-style type conversions int y = (int)x or int y = int(x) in C++ code. But the problem with C type conversions is that they are ambiguous; sometimes they are doing forced conversions (like (int)3.5), sometimes they are doing type conversions (like (int) “hello”).

So we should.

  • Replace C-style value conversions with static_cast, or when a class pointer needs to be explicitly converted up to a parent class pointer.
  • Remove the const qualifier with const_cast.
  • Use reinterpret_cast for unsafe interconversions between pointer types and integers or other pointers. Use only if you know everything you’re doing.

The prefix self-increment (++i), self-subtract operator should be used

The prefix self-increment (++i) is usually more efficient than the postfix self-increment (i++), regardless of the return value. This is because the postfix self-increment (or self-subtract) requires a copy of the value i of the expression. If i is an iterator or other non-numeric type, the cost of copying is higher, but this is only for custom types; for built-in types, most compilers optimize, so there is no difference in efficiency.

Don’t use unsigned integers

Do not use unsigned integers such as uint32_t unless you are representing a byte rather than a value, or you need to define a binary complement overflow. In particular, do not use unsigned types just to indicate that the value will never be negative. Instead, you should use assertions to protect the data.

Beware of integer type conversions and integer promotions (e.g., when operating with int and unsigned int, the former is promoted to unsigned int and may overflow), there are always unintended consequences.

Regarding unsigned integers.

Some people, including some textbook authors, recommend using unsigned types for non-negative numbers. However, in C, this advantage is overwhelmed by the bugs it causes. Look at the following example.

1
for (unsigned int i = foo.Length()-1; i >= 0; --i) ... 

The above loop never exits! Sometimes gcc finds this bug and alerts you, but most of the time it doesn’t. A similar bug occurs when comparing signed variables with unsigned variables.

Replace #define with const, enum, inline as much as possible

A lot of C++ code still retains its C-era habit of using macros, which are not as essential in C++ as they were in C. In C++, macros are not as essential as they were in C. Performance-critical code that used to be expanded with macros can now be replaced with inline functions, and macros that represent constants can be replaced with const variables.

As a common example, let’s say we use the following macro.

1
#define PI 3.14

As in the case of the PI macro definition above, when the program is compiled, the compiler replaces all PI macro definitions in the source code with 3.14 during the preprocessing phase. the program is compiled in the preprocessing phase before the real compilation phase. In some compilers that apply this PI constant, if a compilation error is encountered, then this error message might mention 3.14 instead of PI, which is confusing, especially if the project is large.

Anyway, be very careful when using macros, and try to replace them with inline functions, enumerations and constants.

For naming convention and code style

Different teams have different specifications and code styles, you should try to refer to the original project’s to do, rather than adding a new style yourself, which will feel very strange when reading the code. In order to maintain consistency with the original style of the code, if you are not sure you should discuss with the original author of the code or the person in charge now.

Reference

http://staff.ustc.edu.cn/~tongwh/CG_2019/materials/Google%20C++%20Style%20Guide.pdf