In this page, I will introduce some best practice recommended by Google. We should write readable, efficient, and extendable codes.
Use EXPECT_THAT & ElementsAre & UnorderedElementsAre
It can be more flexible than EXPECT_EQ
.
EXPECT_THAT(Foo(), ElementsAre(1, Gt(0), _, 10));
If no order is required, consider UnorderedElementsAre
.
Test Suite Names and Test Names not Contain Underscore
Prefer Range-Based Loops
If possible, use for (const auto i& : v)
rather than index-based loops for better performance (same reason as avoiding vector<T>&
below).
https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#Res-for-range
Avoid vector<T>&
or string &
When using a pointer (or reference) to a container, it usually contains two levels of pointer arithmetic. Consider using absl::Span
or absl::string_view
instead.
void Foo(vector<T> &a) {
a[0] = 1; // actually it is a->data()[0] = 1
}
// method 1
void Foo(absl::Span<T> a) {
a[0] = 1; // data[0]=1, only one level of pointer arithmetic
}
// method 2
void Foo(vector<T> &a) {
absl::Span<T> b(a);
for (...) {
b[i] = 1;
}
}
Two levels of pointer arithmetic makes it really hard for compilers to do analysis. When read or write data in loops, absl::Span
becomes much faster because of vectorization.
Avoid []
parameters, prefer span
Use absl::Span
instead for better readability and safety.
Avoid Calling new
& delete
Explicitly
Suggest using make_unique
instead.
StrCat
Avoid foo + bar + baz
, use absl::StrCat(foo, bar, baz)
instead. Since for chains of n
string concatenations, in the worst case, it requires O(n)
reallocations.
Order of Includes
Separate headers by sections. Within each section the includes should be ordered alphabetically. No UNIX directory aliases .
(the current directory) or ..
(the parent directory) please.
dir2/foo2.h
(the declaration if applicable)- A blank line
- C system headers (more precisely: headers in angle brackets with the
.h
extension), e.g.,<unistd.h>
,<stdlib.h>
. - A blank line
- C++ standard library headers (without file extension), e.g.,
<algorithm>
,<cstddef>
. - A blank line
- Other libraries’
.h
files. - Your project’s
.h
files
As an example, in foo/server/fooserver.cc
#include "foo/server/fooserver.h"
#include <sys/types.h>
#include <unistd.h>
#include <string>
#include <vector>
#include "base/basictypes.h"
#include "base/commandlineflags.h"
#include "foo/server/bar.h"
Google’s C++ Stype Guide#Names_and_Order_of_Includes
Namespace
- Do not use
using-directive (using namespace foo;)
- With few exceptions, place code in a namespace (include
using-declarations, like using foo::bar;
). - Fully qualify
using-declarations
and namespace aliases; in other cases, please try to avoid fully qualifying unless you must to make it compile; - Don’t introduce sub-namespaces with common names like
std
,util
, ortesting
.
namespace foo {
using ::foo1::bar::Baz;
namespace baz = ::foo2::bar::baz;
}
https://abseil.io/tips/153, https://abseil.io/tips/130
Namespace Lookup
namespace foo {
namespace bar {
void Func() {
Baz::HaHa b;
}
} // namespace bar
} // namespace foo
In C++, lookup on an unqualified name (Baz::HaHa
) will search expanding scopes for a symbol of the same name: first in Func
(the function), then in bar
, then in foo
, then in the global namespace.
If Baz
refers to a global namespace and in bar
there is another namespace called Baz
, but without HaHa
defined, then building will fail, since namespace lookup looked into ::foo::bar::Baz
first, failed, and stopped. Use fully qualified ::Baz::HaHa
will solve the problem.
Reduce Double-Negatives
// bad
if (!disable_b) {
DoA();
} else {
DoB();
}
// good
if (enable_b) {
DoB();
} else {
DoA();
}
Reduce Nesting Levels
Level Depth is better to be no more than 2, which could be achieved by early return or separate functions.
Function Comments
Declaration comments describe use of the function (comments should use the indicative mood like “Returns the area”, rather than the imperative “Return the area”; definition comments describe implementation details.
string_view
Prefer string_view
over const char*
or const string&
as function parameters.
Note thatstring_view
is not necessarily NUL-terminated.
Avoid Boolean Parameters
Prefer enum over boolean parameters. Foo(Feature::kA)
makes more sense than Foo(/*enable_A*/true)
.
Output Parameters
Prefer return value over output parameters (please refer to StatusOr in protobuf, the StatusOr
on Abseil is not open sourced yet).
Also output parameters should be after input parameters.
Prefer span
as Function Parameters
Same as string_view over const char*
or const string&
, prefer span<const T>
over const T*
or const vector<T>&
as function parameters.
Usually pass span
or string_view
by value, since it is not always faster to pass a pointer. See Copy Elision and Pass-by-value in https://abseil.io/tips/117.
Qualifying auto
- Use
const auto&
orauto&
unless a copy is needed; - Use
const auto&
unless you need to modify data; - Use
const auto*
orauto*
to make pointer explicit;
char num = 'a';
char* p1 = #
auto p2 = p1; // p2 is 'char*'
auto* p3 = p1; // p3 is 'char*' (same as bare auto)
Reserve Size in Advance
Call reserve if the final size is known to avoid repeated allocation and the corresponding copy/moves.
Since if a reallocation happens, old values might be copied into the new memory.
Prefer push_back
over emplace_back
Prefer push_back
over emplace_back
unless the performance matters. Since emplace_back
is too powerful.
std::vector<vector<int>> vec;
vec.push_back(1<<20); // compile error
vec.emplace_back(1<<20); // no errors until run-time
https://abseil.io/tips/112, https://abseil.io/tips/65
Global Constants
It is very common that people want to define static const variables in the global namespace.
- One global variable shouldn’t rely on another global in a different translation unit (roughly, a
cc
file), since the order of initialization between translation units is unspecified. - Ensure the One Definition Rule (external linkage rather than internal linkage). This is important when address matters.
- Global constants should be trivially destructible (string is not). As a rule of thumb: a global variable satisfies these requirements if its declaration, considered in isolation, could be
constexpr
.
Constants in Header
Before C++17, use extern const variables.
// constants.h
#ifndef CONSTANTS_H
#define CONSTANTS_H
namespace constants
{
// since the actual variables are inside a namespace, the forward declarations need to be inside a namespace as well
extern const double pi;
extern const double avogadro;
extern const double my_gravity;
}
#endif
// constants.cc
#include "constants.h"
namespace constants
{
// actual global variables
extern const double pi { 3.14159 };
extern const double avogadro { 6.0221413e23 };
extern const double my_gravity { 9.2 }; // m/s^2 -- gravity is light on this planet
}
After C++17, use inline variables.
#ifndef CONSTANTS_H
#define CONSTANTS_H
// define your own namespace to hold constants
namespace constants
{
inline constexpr double pi { 3.14159 }; // note: now inline constexpr
inline constexpr double avogadro { 6.0221413e23 };
inline constexpr double my_gravity { 9.2 }; // m/s^2 -- gravity is light on this planet
// ... other related constants
}
#endif
Constants in Source
constexpr int kNum = 42;
constexpr char kStr[] = "hello";
constexpr std::string_view kAnotherStr = "world";
https://www.learncpp.com/cpp-tutorial/global-constants-and-inline-variables/
https://google.github.io/styleguide/cppguide.html#Static_and_Global_Variables
Pass Primitives & Enums by Value
Pass primitives and enumerators (including enum class
) by value, since usually primitives are smaller than pointers. Passing by pointers or references also makes codes harder to be optimized by compiler.