Note: this is a work in progress.
This is largely based on Google C++ Style Guide. If you have time, you should read it. C++ programmer of any level can learn from it.
Google C++ Style Guide has clearly stated its goals, which provides the justification of the rules in the style guide. Dr. Titus Winters had a very good talk on this. Some of the goals that worth mentioning, especially for the new C++ programmers are:
-
Optimize for the READER, not the writer.
Realize that most of our time will be spent on reading the code than writing it.
-
Avoid surprising or dangerous constructs.
Black magics may look awesome, but they tend to increase the risk of bugs and incorrectness (not only when you are developing it, but also when you or someone else is maintaining it). "Don't be clever".
-
Be consistent (with existing code).
This contributes to both the readability and the possibility of having automation tools.
We will restrain ourselves from talking much about format such as indentation. It is not as important, and seriously you should make good use of your favorite code editor, and the handy clang-format. See C++ Programmer's Toolbox for details.
-
Header guards vs #pragma once
Google style guide requires header guards. We should prefer
#pragma once
instead.#pragma once
is not part of the standard. However, all mainstream compilers have been supporting it for years.- Some codebase may require header guards for consistency, but new code base may not have that constraint.
#pragma once
reduces the possibility of bugs (e.g. when moving files around), and is arguably more readable.- Details and example.
-
Forward Declarations
See Google Style Guide.
-
Inline Functions
Rule of thumb: do not inline a function if it is more than 10 lines long.
-
#include order
#include "foo/server/fooserver.h" // corresponding header #include <sys/types.h> // c system headers #include <unistd.h> #include <hash_map> // c++ system headers #include <vector> #include "base/basictypes.h" // other headers #include "base/commandlineflags.h" #include "foo/server/bar.h"
All in alphabetic order.
- Easy to maintain/read. The maintainer/reader can find the corresponding header file quickly.
- We do not have to stick to this rule strictly, although we need to be consistent about the order in our project.
-
Unnamed Namespace
- Use them in
.cpp
file to hide functions or variables you do not want expose. - Do not use them in
.h
files. - Sometimes you may want to expose internal functions or variables just for testing purpose. In this case it is better to declare them in internal-only namespaces.
- Use them in
-
Never do using namespace foo;
-
This pollutes the namespace, and can lead to hard-to-resolve compiler errors and bugs.
-
If you really have something like
a::really::long::nested::name::space
, you can probably use namespace alias in a.cpp
file:namespace short_name = a::really::long::nested::name::space;
Do not use namespace alias in header files except in internal-only namespaces. This is because such aliases will affect every file that includes this header file.
-
-
Avoid nested namespaces that match well-known top-level namespaces.
Namespace collision may happen if you use
util
to refer tomy_library::util
withinnamespace my_library
, while there is an existing top-level namespace calledutil
. Even if no collision happens, this confuses the reader. Therefore,- Try not to name your namespace
my_library::util
in this case. - Refer to the top-level
util
as::util
to explicitly say "top-level".
Common top-level namespaces names that are prone to this are
util
,base
,aux
, etc. - Try not to name your namespace
- Copy Constructor and Move Constructor
- If you provide copy constructor and/or move constructor,
provide the corresponding
operator=
overload as well.
- If you provide copy constructor and/or move constructor,
provide the corresponding
- Inheritance
- All inheritance should be public. If you want to do private inheritance, you should be including an instance of the base class as a member instead.
- Do not overuse implementation inheritance. Composition is often more appropriate. Try to restrict use of inheritance to the "is-a" case: Bar subclasses Foo if it can reasonably be said that Bar "is a kind of" Foo.
- If you find yourself wanting to use multiple inheritance, THINK TWICE.
- Access Control
- Data members are private, except when they are static const.
- For technical reasons, we allow data members of a test fixture class to be protected when using Google Test).
- Declaration Order
public
,protected
and thenprivate
.- In each section, group similar declarations together, prefer
the order:
*
typedef
andusing
*struct
andclass
* factory functions * constructors *operator=
* destructors * methods * data members
-
Parameters
- Ordering: Input and then Output.
- All references must be const, and this is the recommended way to pass parameters.
- For output parameters, pass pointers.
-
Default Argument
- Not recommended for readability issue. Do not use unless you have to.
-
Trailing Return Type Syntax
- When in lambda. Period.
-
Return Value of Complicated Type
Returning values of simple types such as
int
,int64_t
,bool
may involves copy, but we hardly care. However it is rather important to avoid copy when returning values of complicated types because it can be rather expensive, e.g.std::vector<int>
.One of the common pattern is to pass in a
std::vector<int>
pointer as parameter and construct it in place as below:void DoWork(..., std::vector<int> *result) { ... result->push_back(...); ... result->push_back(...); ... }
I think this pattern should be discouraged in favor of return value optimization, because the latter is not only less error-prone, but also much more readable.
- DO NOT throw exceptions. Returns error code, and let the upper
level caller handles them.
- The benefit is that we will be forced to handle every error, and be free of surprise exits. Such exits are especially bad in multi-thread or multi-process programs.
- Constructors should avoid any code that can fail.
- As stated above, we need to return error code on failure. However, constructors can not do that.
- Solution: Use factory functions, or use
Init()
function to do the lift.
-
Thread-safe Local Static Initialization
In the following function:
void SomeFunction() { static SomeType some_static_variable(); ... }
If multiple control flows enter the function concurrently, is there a risk of race condition?
The answer is no, if compiled with C++11.
In fact, if control enters the declaration concurrently while the variable is being initialized, the concurrent execution shall wait for completion of the initialization.
This property can be taken advantage of to do more complicated object (lazy) initialization in a thread-safe way, together with the lambda functions. For example, the following code initializes a local static vector in a thread-safe way.
void SomeFunction() { static std::unique_ptr<vector<int>> my_list([]() { // Note that this lambda function is called within the declaration, // therefore is thread-safe. vector<int> *tmp_list = new vector<int>(); tmp_list.push_back(1); tmp_list.push_back(2); ... return tmp_list; }()); }
I think for naming we should stick to Google C++ Style Guide.
There are many available tools for C++, and it is always good to have them in your pocket. Some of them help enforce the rules automatically. Use them to free yourself from worrying about the format and focus on more important stuff.
- clang
- clang-format
- asan (Address Sanitizer)
- rtags
-
Use the ones in standard library rather than the third-party implementation (e.g. boost) if you have a choice. This helps reduce both runtime dependencies and compile-time dependencies, and it promotes readability.
-
Use
const
whenever it makes sense. With C++11,constexpr
is a better choice for some uses ofconst
-
<stdint.h>
defines types likeint16_t
,uint32_t
,int64_t
, etc. You should always use those in preference to short, unsigned long long and the like, when you need a guarantee on the size of an integer. -
Macros damage readability. Do not use them unless the benefit is huge enough to compensate the loss.
-
auto
is permitted when it promotes readability. -
switch
cases may have scopes:switch (var) { case 0: { // 2 space indent ... // 4 space indent break; } case 1: { ... break; } default: { assert(false); } }