欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

【C/C++】Google 出品的代码规范(Google C++ Style Guide)

程序员文章站 2022-03-05 10:41:59
...

文章目录

I. 开篇, 一图胜千言

【C/C++】Google 出品的代码规范(Google C++ Style Guide)

此图来自 一张图总结Google C++编程规范(Google C++ Style Guide)

具体细节,娓娓道来。

II. 分门别类, 娓娓道来

本章来自于官方文档的摘录. Google C++ Style Guide

Principles 原则

  • Style rules should pull their weight.
  • Optimize for the reader, not the writer
  • Be consistent with existing code
  • Be consistent with the broader C++ community when appropriate
  • Avoid surprising or dangerous constructs
  • Avoid constructs that our average C++ programmer would find tricky or hard to maintain
  • Be mindful of our scale
  • Concede to optimization when necessary

Header Files 头文件

Self-contained Headers

Header files should be self-contained (compile on their own) and end in .h. Non-header files that are meant for inclusion should end in .inc and be used sparingly.

The #define Guard #define 保护

  • 按文件夹顺序: <PROJECT>_<PATH>_<FILE>_H_.

. For example, the file foo/src/bar/baz.h in project foo should have the following guard:

#ifndef FOO_BAR_BAZ_H_
#define FOO_BAR_BAZ_H_

...

#endif  // FOO_BAR_BAZ_H_

Forward Declarations 前置声明

A “forward declaration” is a declaration of a class, function, or template without an associated definition.

  • 尽量不用, 需要什么就 #include

Inline Functions 内联函数

  • 小于 10 行的简单直接的小程序, 才写成内联函数

Names and Order of Includes

In dir/foo.cc or dir/foo_test.cc, whose main purpose is to implement or test the stuff in dir2/foo2.h, order your includes as follows:

  1. dir2/foo2.h.
  2. A blank line
  3. C system files.
  4. C++ system files.
  5. A blank line
  6. Other libraries’ .h files.
  7. Your project’s .h files.

For example, the includes in google-awesome-project/src/foo/internal/fooserver.cc might look like this:

#include "foo/server/fooserver.h"

#include <sys/types.h>
#include <unistd.h>
#include <vector>

#include "base/basictypes.h"
#include "base/commandlineflags.h"
#include "foo/server/bar.h"
  • system-specific code needs conditional includes. 后置
#include "foo/public/fooserver.h"

#include "base/port.h"  // For LANG_CXX11.

#ifdef LANG_CXX11
#include <initializer_list>
#endif  // LANG_CXX11

Scope 域

Namespaces 命名空间

  • 所有逻辑代码一律放在 namespace 里
// In the .h file
namespace mynamespace {

// All declarations are within the namespace scope.
// Notice the lack of indentation.
class MyClass {
 public:
  ...
  void Foo();
};

}  // namespace mynamespace
// In the .cc file
namespace mynamespace {

// Definition of functions is within scope of the namespace.
void MyClass::Foo() {
  ...
}

}  // namespace mynamespace
  • 一律禁止使用 using namespace XXX, 如 using namespace std
  • 可以使用 using, 如 using std::string
  • 不使用 inline namespace.

Nonmember, Static Member, and Global Functions

  • 放在单独的 namespace 中

Local Variables

  • Place a function’s variables in the narrowest scope possible, and initialize variables in the declaration.
int i;
i = f();      // Bad -- initialization separate from declaration.
int j = g();  // Good -- declaration has initialization.


std::vector<int> v;
v.push_back(1);  // Prefer initializing using brace initialization.
v.push_back(2);
std::vector<int> v = {1, 2};  // Good -- v starts initialized.


//Variables needed for if, while and for statements should normally be declared within those statements, 
// so that such variables are confined to those scopes. E.g.:

while (const char* p = strchr(str, '/')) str = p + 1;

// There is one caveat: if the variable is an object, its constructor is invoked every time 
// it enters scope and is created, and its destructor is invoked every time it goes out of scope.

// Inefficient implementation:
for (int i = 0; i < 1000000; ++i) {
  Foo f;  // My ctor and dtor get called 1000000 times each.
  f.DoSomething(i);
}
// It may be more efficient to declare such a variable used in a loop outside that loop:

Foo f;  // My ctor and dtor get called once each.
for (int i = 0; i < 1000000; ++i) {
  f.DoSomething(i);
}

Static and Global Variables

Classes 类

Doing Work in Constructors

  • 绝不能调用虚函数 Avoid virtual method calls in constructors
  • 决不能调用可能失败的操作 avoid initialization that can fail if you can’t signal an error.
  • 可以把上面的操作添加到一个 Init() 函数中, 注意 Init 不要滥用
  • 推荐使用 factory function, 具体理由参见 TotW #42 类似这样
// foo.h
class Foo {
 public:
  // Factory method: creates and returns a Foo.
  // May return null on failure.
  static std::unique_ptr<Foo> Create();

  // Foo is not copyable.
  Foo(const Foo&) = delete;
  Foo& operator=(const Foo&) = delete;

 private:
  // Clients can't invoke the constructor directly.
  Foo();
};

// foo.c
std::unique_ptr<Foo> Foo::Create() {
  // Note that since Foo's constructor is private, we have to use new.
  return absl::WrapUnique(new Foo());
}

Implicit Conversions

  • 单参数构造函数必须使用 explicit
  • 特例: copy and move constructors should not be explicit since they do not perform type conversion
  • 特例: Implicit conversions can sometimes be necessary and appropriate for types that are designed to transparently wrap other types. In that case, contact your project leads to request a waiver of this rule.
  • Constructors that cannot be called with a single argument may omit explicit.
  • Constructors that take a single std::initializer_list parameter should also omit explicit, in order to support copy-initialization (e.g. MyType m = {1, 2}????.

Copyable and Movable Types

  • A movable type is one that can be initialized and assigned from temporaries.
  • A copyable type is one that can be initialized or assigned from any other object of the same type (so is also movable by definition), with the stipulation that the value of the source does not change.
  • int and string are examples of movable types that are also copyable.
  • std::unique_ptr<int> is an example of a movable but not copyable type (since the value of the source std::unique_ptr<int> must be modified during assignment to the destination)
  • A class’s public API should make explicit whether the class is copyable, move-only, or neither copyable nor movable. Support copying and/or moving if these operations are clear and meaningful for your type.
  • 怎么限制, 以以下的代码形式
class Copyable {
 public:
  Copyable(const Copyable& rhs) = default;
  Copyable& operator=(const Copyable& rhs) = default;

  // The implicit move operations are suppressed by the declarations above.
};

class MoveOnly {
 public:
  MoveOnly(MoveOnly&& rhs);
  MoveOnly& operator=(MoveOnly&& rhs);

  // The copy operations are implicitly deleted, but you can
  // spell that out explicitly if you want:
  MoveOnly(const MoveOnly&) = delete;
  MoveOnly& operator=(const MoveOnly&) = delete;
};

class NotCopyableOrMovable {
 public:
  // Not copyable or movable
  NotCopyableOrMovable(const NotCopyableOrMovable&) = delete;
  NotCopyableOrMovable& operator=(const NotCopyableOrMovable&)
      = delete;

  // The move operations are implicitly disabled, but you can
  // spell that out explicitly if you want:
  NotCopyableOrMovable(NotCopyableOrMovable&&) = delete;
  NotCopyableOrMovable& operator=(NotCopyableOrMovable&&)
      = delete;
};

简写版, 在类的private中添加空的拷贝构造函数和赋值操作,并且只有声明,不进行定义

// the old way
#define DISALLOW_COPY_AND_ASSIGN(Type) \
    Type(const Type&);                 \
    void operator = (const Type&)

class Foo
{
public:
    Foo(int f);
    ~Foo();
private:
    DISALLOW_COPY_AND_ASSIGN(Foo);
};

Structs vs. Classes

  • Struct 只用来保存数据组合, 不能带有任何函数
  • 其他所有情况都用 class
  • 特例 : For consistency with STL, you can use struct instead of class for functors and traits.

Inheritance

  • 组合比继承更好 Composition is often more appropriate than inheritance.
  • is-a 情况下可以使用继承. 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.
  • 必须使用继承时, 确保是 public 继承. When using inheritance, make it public.
  • data 始终是private 的, 可以被子类访问的 method 用 protected : Limit the use of protected to those member functions that might need to be accessed from subclasses
  • 重定义派生的虚函数时,在派生类中明确声明其为virtual : Explicitly annotate overrides of virtual functions or virtual destructors with exactly one of an override or (less frequently) final specifier. Do not use virtual when declaring an override. Rationale: A function or destructor marked override or final that is not an override of a base class virtual function will not compile, and this helps catch common errors. The specifiers serve as documentation; if no specifier is present, the reader has to check all ancestors of the class in question to determine if the function or destructor is virtual or not.
  • Multiple inheritance is permitted, but multiple implementation inheritance is strongly discouraged. 可以考虑使用接口, 纯接口必须以Interface为后缀, 如 RunnableInterface

Operator Overloading

  • Overload operators judiciously. Do not create user-defined literals.
  • Define overloaded operators only if their meaning is obvious, unsurprising, and consistent with the corresponding built-in operators. For example, use | as a bitwise- or logical-or, not as a shell-style pipe.
  • prefer to define ==, =, and <<, rather than Equals(), CopyFrom(), and PrintTo().
  • Do not overload &&, ||, , (comma), or unary &. Do not overload operator"", i.e. do not introduce user-defined literals.
  • don’t define operator overloads just because other libraries expect them. For example, if your type doesn’t have a natural ordering, but you want to store it in a std::set, use a custom comparator rather than overloading <.

Access Control

  • Make classes’ data members private, unless they are static const

Declaration Order

  • Group similar declarations together, placing public parts earlier.
  • A class definition should usually start with a public: section, followed by protected:, then private:. Omit sections that would be empty.
  • Within each section, generally prefer grouping similar kinds of declarations together, and generally prefer the following order:
    • types (including typedef, using, and nested structs and classes),
    • constants,
    • factory functions,
    • constructors,
    • assignment operators,
    • destructor,
    • all other methods,
    • data members.
  • Do not put large method definitions inline in the class definition. Usually, only trivial or performance-critical, and very short, methods may be defined inline.

Functions 函数

Output Parameters

  • The output of a C++ function is naturally provided via a return value and sometimes via output parameters.
  • Prefer using return values instead of output parameters since they improve readability and oftentimes provide the same or better performance.
  • If output-only parameters are used they should appear after input parameters.
  • Input parameters are usually values or const references, while output and input/output parameters will be pointers to non-const.
  • When ordering function parameters, put all input-only parameters before any output parameters.
  • Parameters that are both input and output (often classes/structs) muddy the waters, and, as always, consistency with related functions may require you to bend the rule.

Write Short Functions

  • Prefer small and focused functions.
  • 一般而言, 超过 40 行的代码就需要考虑拆分了.
  • Even if your long function works perfectly now, someone modifying it in a few months may add new behavior. This could result in bugs that are hard to find. Keeping your functions short and simple makes it easier for other people to read and modify your code.

Reference Arguments

  • All parameters passed by lvalue reference must be labeled const.
void Foo(const string &in, string *out);
  • However, there are some instances where using const T* is preferable to const T& for input parameters. For example:
    • You want to pass in a null pointer.
    • The function saves a pointer or reference to the input.

Function Overloading

  • Use overloaded functions (including constructors) only if a reader looking at a call site can get a good idea of what is happening without having to first figure out exactly which overload is being called.
  • 比如下面这种情况可以采用, 但是更高效的方法是调用 std::string_view.
class MyClass {
 public:
  void Analyze(const string &text);
  void Analyze(const char *text, size_t textlen);
};
  • You may overload a function when there are no semantic differences between variants. These overloads may vary in types, qualifiers, or argument count. However, a reader of such a call must not need to know which member of the overload set is chosen, only that something from the set is being called. If you can document all entries in the overload set with a single comment in the header, that is a good sign that it is a well-designed overload set.

Default Arguments

  • Default arguments are banned on virtual functions, where they don’t work properly,
  • Default arguments are allowed on non-virtual functions when the default is guaranteed to always have the same value.
  • When in doubt, use overloads.

Trailing Return Type Syntax

C++ 11 引入了新的函数声明方法,

auto foo(int x) -> int;
  • 仅在必须使用上述方式的地方使用这种方式, 如 lambda
  • 大多数情况下, 仍然采用传统方式. int foo(int x);

Google-Specific Magic

Ownership and Smart Pointers

  • Ownership is a bookkeeping technique for managing dynamically allocated memory (and other resources). The owner of a dynamically allocated object is an object or function that is responsible for ensuring that it is deleted when no longer needed. Ownership can sometimes be shared, in which case the last owner is typically responsible for deleting it. Even when ownership is not shared, it can be transferred from one piece of code to another.
  • “Smart” pointers are classes that act like pointers, e.g. by overloading the * and -> operators. Some smart pointer types can be used to automate ownership bookkeeping, to ensure these responsibilities are met.
  • std::unique_ptr is a smart pointer type introduced in C++11, which expresses exclusive ownership of a dynamically allocated object; the object is deleted when the std::unique_ptr goes out of scope. It cannot be copied, but can be moved to represent ownership transfer.
  • std::shared_ptr is a smart pointer type that expresses shared ownership of a dynamically allocated object. std::shared_ptrs can be copied; ownership of the object is shared among all copies, and the object is deleted when the last std::shared_ptr is destroyed.
  • Never use std::auto_ptr.
  • Prefer to use std::unique_ptr to make ownership transfer explicit, 例如
std::unique_ptr<Foo> FooFactory();
void FooConsumer(std::unique_ptr<Foo> ptr);

cpplint

  • Use cpplint.py to detect style errors.
  • cpplint.py is a tool that reads a source file and identifies many style errors. It is not perfect, and has both false positives and false negatives, but it is still a valuable tool. False positives can be ignored by putting// NOLINT at the end of the line or // NOLINTNEXTLINE in the previous line.

Other C++ Features

Rvalue References

  • Rvalue references are a type of reference that can only bind to temporary objects. The syntax is similar to traditional reference syntax. For example, void f(string&& s);declares a function whose argument is an rvalue reference to a string.
  • You may use rvalue references to define move constructors and move assignment operators

Friends

  • We allow use of friend classes and functions, within reason.
  • Friends should usually be defined in the same file so that the reader does not have to look in another file to find uses of the private members of a class.
  • A common use of friend is to have a FooBuilder class be a friend of Foo so that it can construct the inner state of Foo correctly, without exposing this state to the world.
  • In some cases it may be useful to make a unittest class a friend of the class it tests.

Exceptions

  • We do not use C++ exceptions.

noexcept

  • The noexcept specifier is used to specify whether a function will throw exceptions or not. If an exception escapes from a function marked noexcept, the program crashes via std::terminate.
  • The noexcept operator performs a compile-time check that returns true if an expression is declared to not throw any exceptions.
  • Specify noexcept when it is useful and correct.

Run-Time Type Information(RTTI)

  • Avoid using RTTI
  • RTTI allows a programmer to query the C++ class of an object at run time. This is done by use of typeid or dynamic_cast.

Casting

  • Use C+±style casts like static_cast<float>(double_value)
  • Or brace initialization for conversion of arithmetic types like int64 y = int64{1} << 42
  • Do not use C-style cast formats likeint y = (int)x or int y = int(x)
  • Decisions :
    • Use brace initialization to convert arithmetic types (e.g. int64{x}). This is the safest approach because code will not compile if conversion can result in information loss. The syntax is also concise.
    • Use static_cast as the equivalent of a C-style cast that does value conversion, when you need to explicitly up-cast a pointer from a class to its superclass, or when you need to explicitly cast a pointer from a superclass to a subclass. In this last case, you must be sure your object is actually an instance of the subclass.
    • Use const_cast to remove the const qualifier
    • Use reinterpret_cast to do unsafe conversions of pointer types to and from integer and other pointer types. Use this only if you know what you are doing and you understand the aliasing issues.

Streams

  • Streams are the standard I/O abstraction in C++, as exemplified by the standard header <iostream>. They are widely used in Google code, but only for debug logging and test diagnostics.
  • Use streams where appropriate, and stick to “simple” usages. Overload << for streaming only for types representing values, and write only the user-visible value, not any implementation details.

Preincrement and Predecrement

  • Use prefix form (++i) of the increment and decrement operators with iterators and other template objects.

Use of const

  • Use const whenever it makes sense. With C++11, constexpr is a better choice for some uses of const.

Use of constexpr

  • Some variables can be declared constexpr to indicate the variables are true constants, i.e. fixed at compilation/link time. Some functions and constructors can be declared constexpr which enables them to be used in defining a constexpr variable.
  • constexpr definitions enable a more robust specification of the constant parts of an interface.
  • Use constexpr to specify true constants and the functions that support their definitions.
  • 不要滥用 Avoid complexifying function definitions to enable their use with constexpr.
  • Do not use constexpr to force inlining.

Integer Types

  • C++ 本身未定义数据的位数. C++ does not specify the sizes of integer types like int. Typically people assume that short is 16 bits, int is 32 bits, long is 32 bits and long long is 64 bits.
  • 一般的小的数字, 心中有数不会太出格的数, 用 int. 比如循环
  • 其余的, 需要比较精确的限定整型数大小范围的, 使用 stdint.h 中定义的 int16_t, uint32_t, int64_t 等.
  • 尽量避免使用 unsigned

64-bit Portabaility

  • Code should be 64-bit and 32-bit friendly. Bear in mind problems of printing, comparisons, and structure alignment
  • Remember that sizeof(void *) != sizeof(int). Use intptr_t if you want a pointer-sized integer.

Preprocessor Macros

  • Avoid defining macros, especially in headers;
  • prefer inline functions, enums, and const variables. Name macros with a project-specific prefix.
  • Do not use macros to define pieces of a C++ API.

0 and nullptr/NULL

Use

  • 0 for integers
  • 0.0 for reals
  • nullptr for void pointers, NULL for C++ 03 or earlier
  • \\0 for null chars.

sizeof

  • Prefer sizeof(varname) to sizeof(type).

auto

  • Use auto to avoid type names that are noisy, obvious, or unimportant - cases where the type doesn’t aid in clarity for the reader. Continue to use manifest type declarations when it helps readability.
for (const auto& item : some_map) {
  const KeyType& key = item.first;
  const ValType& value = item.second;
  // The rest of the loop can now just refer to key and value,
  // a reader can see the types in question, and we've avoided
  // the too-common case of extra copies in this iteration.
}

Braced Initializer List

  • In C++ 03, aggregate types (arrays and structs with no constructor) could be initalized with braced initializer list, like
struct Point {int x; int y};
Point p = {1,2};
  • In C++ 11, this syntax was generalized to all object type, known as braced-init-list, i.e.
//Vector takes a braced-init-list of elements
std::vector<string> v{"foo", "bar"};

//Basically the same.
std::vector<string> v = {"foo", "bar"};

//Usable with `new` expressions
auto p = new std::vector<string>{"foo", "bar"};

// A map can take  a list of pairs. Nested braced-init-list work
std::map<int, string> m = {{1, "one"},{2, "two"}};

// A braced-init-list can be implicitly converted to a return type
std::vector<int> test_function() {return {1,2,3};}

// Iterate over a braced-init-list
for(int i : {-1,-2,-3}) {}

//Call a function using a braced-init-list
void TestFunction2(std::vector<int> v) {}
TestFunction2({1,2,3});
  • A user-defined type can also define a constuctor and/or assignment operator that take std::initializer_list<T>, which is automatically created from braced-init-list.
class MyType {
    public:
      //std::initializer_list references the underlying init list.
      //It should be passed by value
      MyType(std::initializer_list<int> init_list){
          for(int i:init_list) append(i);
      }
      
      MyType& operator=(std::initializer_list<int> init_list) {
          clear();
          for(int i:init_list) append(i);
      }
};
MyType m1{1,2,3};
MyType m2 = {4,5,6};
  • Finally, brace initialization can also call ordinary constructors of data type, even if they do not have std::initalizer_list<T> constructors.
// Calls ordinary constuctors as long as MyOtherType has no
// std::initializer_list constructor.
class MyOtherType {
    public:
    	explicit MyOtherType(string);
    	MyOtherType(int, string);
};

MyOtherType m = {1, "one"};
// If the constructor is explicit, you can't use the "= {}" form
MyOtherType m2{"b"};
  • warning : Nerver assign a braced-init-list to an auto local variable.
auto d = {1.23}; //bad! d is a std::initializer_list<double>
auto d = double{1.23}; //Good! d is a double

Lambda Expressions

  • Lambda expressions are a concise way of creating anonymous function objects.
  • They’re often useful when passing functions as arguments.
  • Lambdas were introduced in C++11 along with a set of utilities for working with function objects, such as the polymorphic wrapper std::function.
  • They further allow capturing variables from the enclosing scope either explicitly by name, or implicitly using a default capture. Explicit captures require each variable to be listed, as either a value or reference capture
// A simple lambda expression
std::sort(v.begin(), v.end(), [](int x, int y) {
  return Weight(x) < Weight(y);
});


// Good : explicit capture
{
  Foo foo;
  ...
  executor->Schedule([&foo] { Frobnicate(foo); })
  ...
}
// BETTER - The compile will fail if `Frobnicate` is a member
// function, and it's clearer that `foo` is dangerously captured by
// reference.


// Bad : implicit capture
{
  Foo foo;
  ...
  executor->Schedule([&] { Frobnicate(foo); })
  ...
}
// BAD! The fact that the lambda makes use of a reference to `foo` and
// possibly `this` (if `Frobnicate` is a member function) may not be
// apparent on a cursory inspection. If the lambda is invoked after
// the function returns, that would be bad, because both `foo`
// and the enclosing object could have been destroyed.

Template metaprogramming

  • Template metaprogramming refers to a family of techniques that exploit the fact that the C++ template instantiation mechanism is Turing complete and can be used to perform arbitrary compile-time computation in the type domain.
  • Template metaprogramming sometimes allows cleaner and easier-to-use interfaces than would be possible without it, but it’s also often a temptation to be overly clever. It’s best used in a small number of low level components where the extra maintenance burden is spread out over a large number of uses.
  • Avoid complicated template programming.

Boost

  • The Boost library collection is a popular collection of peer-reviewed, free, open-source C++ libraries.
  • Use only approved libraries from the Boost library collection.
    • Call Traits from boost/call_traits.hpp
    • Compressed Pair from boost/compressed_pair.hpp
    • The Boost Graph Library (BGL) from boost/graph, except serialization (adj_list_serialize.hpp) and parallel/distributed algorithms and data structures (boost/graph/parallel/* and boost/graph/distributed/*).
    • Property Map from boost/property_map, except parallel/distributed property maps (boost/property_map/parallel/*).
    • Iterator from boost/iterator
    • The part of Polygon that deals with Voronoi diagram construction and doesn’t depend on the rest of Polygon: boost/polygon/voronoi_builder.hpp, boost/polygon/voronoi_diagram.hpp, and boost/polygon/voronoi_geometry_type.hpp
    • Bimap from boost/bimap
    • Statistical Distributions and Functions from boost/math/distributions
    • Special Functions from boost/math/special_functions
    • Multi-index from boost/multi_index
    • Heap from boost/heap
    • The flat containers fromContainer: boost/container/flat_map, and boost/container/flat_set
    • Intrusive from boost/intrusive.
    • The boost/sort library.
    • Preprocessor from boost/preprocessor.

std::hash

  • std::hash<T> is the function object that the C++11 hash containers use to hash keys of type T, unless the user explicitly specifies a different hash function. For example, std::unordered_map<int, string> is a hash map that uses std::hash<int> to hash its keys, whereas std::unordered_map<int, string, MyIntHash> uses MyIntHash.

  • std::hash is defined for all integral, floating-point, pointer, and enum types, as well as some standard library types such as string and unique_ptr. Users can enable it to work for their own types by defining specializations of it for those types.

  • Do not define specializations of std::hash.

C++ 11

  • Use libraries and language extensions from C++11 when appropriate.
  • Consider portability to other environments before using C++11 features in your project.
  • the following C++11 features may not be used
    • Compile-time rational numbers (<ratio>), because of concerns that it’s tied to a more template-heavy interface style.
    • The <cfenv> and<fenv.h> headers, because many compilers do not support those features reliably.

Nonstandard Extensions

  • Nonstandard extensions to C++ may not be used unless otherwise specified.

Aliases

  • 在 C++ 中, 可以用以下方式创建 aliases
typedef Foo Bar;
using Bar = Foo;
using other_namespace::Foo;
  • In new code, using is preferable to typedef, because it provides a more consistent syntax with the rest of C++ and works with templates.
  • Good ones
namespace mynamespace {
// Used to store field measurements. DataPoint may change from Bar* to some internal type.
// Client code should treat it as an opaque pointer.
using DataPoint = foo::Bar*;

// A set of measurements. Just an alias for user convenience.
using TimeSeries = std::unordered_set<DataPoint, std::hash<DataPoint>, DataPointComparator>;
}  // namespace mynamespace
  • Bad ones
namespace mynamespace {
// Bad: none of these say how they should be used.
using DataPoint = foo::Bar*;
using std::unordered_set;  // Bad: just for local convenience
using std::hash;           // Bad: just for local convenience
typedef unordered_set<DataPoint, hash<DataPoint>, DataPointComparator> TimeSeries;
}  // namespace mynamespace
  • local convenience aliases are fine in function definitions, private sections of classes, explicitly marked internal namespaces, and in .cc files:
// In a.cc file
using foo::Bar;

Naming 命名

命名风格

  • 最重要的是连贯性. The rules are the rules!
  • 其次是清晰性.

一般规则

  • 名字是要达意的, 尽量不要用缩写, 以避免增加理解难度.
  • As a rule of thumb, an abbreviation is probably OK if it’s listed in Wikipedia.
  • Note that certain universally-known abbreviations are OK, such as i for an iteration variable and T for a template parameter.

Good ones :

int price_count_reader;    // No abbreviation.
int num_errors;            // "num" is a widespread convention.
int num_dns_connections;   // Most people know what "DNS" stands for.
int lstm_size;             // "LSTM" is a common machine learning abbreviation.

Bad ones

int n;                     // Meaningless.
int nerr;                  // Ambiguous abbreviation.
int n_comp_conns;          // Ambiguous abbreviation.
int wgc_connections;       // Only your group knows what this stands for.
int pc_reader;             // Lots of things can be abbreviated "pc".
int cstmr_id;              // Deletes internal letters.
FooBarRequestInfo fbri;    // Not even a word.

File Names

  • 全小写
  • _
  • 成对儿出.
  • Be unique. http_server_logs.h rather than log.h
//For a class called MyUsefulClass
my_useful_class.h
my_useful_class.cc

Type Names

  • Type : classes, structs, type aliases, enums, and type template parameters
  • Rules : Type names should start with a capital letter and have a capital letter for each new word. No underscores.
// classes and structs
class UrlTable { ...
class UrlTableTester { ...
struct UrlTableProperties { ...

// typedefs
typedef hash_map<UrlTableProperties *, string> PropertiesMap;

// using aliases
using PropertiesMap = hash_map<UrlTableProperties *, string>;

// enums
enum UrlTableErrors { ...

Variable Names

  • 一般变量, lowcase + _
string table_name;  // OK - uses underscore.
string tablename;   // OK - all lowercase.
string tableName;   // Bad - mixed case.
  • const 常量, k打头, Underscores can be used as separators in the rare cases where capitalization cannot be used for separation.
const int kDaysInAWeek = 7;
const int kAndroid8_0_0 = 24;  // Android 8.0.0

Class Data Members

  • ends with _
class TableInfo {
  ...
 private:
  string table_name_;  // OK - underscore at end.
  string tablename_;   // OK.
  static Pool<TableInfo>* pool_;  // OK.
};

Struct Data Members

  • no _ like class
struct UrlTableProperties {
  string name;
  int num_entries;
  static Pool<UrlTableProperties>* pool;
};

Function Names

一般地,

AddTableEntry()
DeleteUrl()
OpenFileOrDie()

特殊的, setters & getters could be named like variables.

int count() const;
void set_count(int count)

Namespace Names

  • Namespace names are all lower-case.
  • Top-level namespace names are based on the project name .
  • Avoid collisions between nested namespaces and well-known top-level namespaces.

Macro Names

  • 尽量不用
  • 用的话, capitals + _
#define ROUND(x) ...
#define PI_ROUNDED 3.0

Enumerator Names

  • like macros, 但要竭力避免名字冲突. Google 现在推荐 按照 const 的方式写,但是难以接受
enum AlternateUrlTableErrors {
  OK = 0,
  OUT_OF_MEMORY = 1,
  MALFORMED_INPUT = 2,
};
  • 或许可以加上前缀
enum AlternateUrlTableErrors {
  ENUM_ALTERNATED_URL_TABLE_EROOR_STATE_OK = 0,
};

Exceptions to Naming Rules

正在使用一个库, 那么可以 follow 它的命名方式.

sparse_hash_map // STL-like entity; follows STL naming conventions

Comments 注释

注释风格

  • 统一风格,只用 //.
  • 注意对齐和美观.
  • 不要写废话, 尽量别BB

Bad

// Find the element in the vector.  <-- Bad: obvious!
auto iter = std::find(v.begin(), v.end(), element);
if (iter != v.end()) {
  Process(element);
}

Good

// Process "element" unless it was already processed.
auto iter = std::find(v.begin(), v.end(), element);
if (iter != v.end()) {
  Process(element);
}

Best

if (!IsAlreadyProcessed(element)) {
  Process(element);
}

文件注释

在文件的开头加上版权公告, 然后是文件内容描述.

// Copyright 2018 Jingxi Inc.
// Licences (MIT / BSD / ...)
// Author : Shiye (aaa@qq.com)
//
// This is ...

类注释

类的注释要写清楚类的功能, 用法, 和 注意事项, 最好能给一个小栗子.

// Iterates over the contents of a GargantuanTable.
// Example:
//    GargantuanTableIterator* iter = table->NewIterator();
//    for (iter->Seek("foo"); !iter->done(); iter->Next()) {
//      process(iter->key(), iter->value());
//    }
//    delete iter;
class GargantuanTableIterator {
  ...
};

函数注释

函数声明 declaration

在函数声明处描述函数的功能, 信息包括

  • What the inputs and outputs are.
  • For class member functions: whether the object remembers reference arguments beyond the duration of the method call, and whether it will free them or not.
  • If the function allocates memory that the caller must free.
  • Whether any of the arguments can be a null pointer.
  • If there are any performance implications of how a function is used.
  • If the function is re-entrant. What are its synchronization assumptions?
// Returns an iterator for this table.  It is the client's
// responsibility to delete the iterator when it is done with it,
// and it must not use the iterator once the GargantuanTable object
// on which the iterator was created has been deleted.
//
// The iterator is initially positioned at the beginning of the table.
//
// This method is equivalent to:
//    Iterator* iter = table->NewIterator();
//    iter->Seek("");
//    return iter;
// If you are going to immediately seek to another place in the
// returned iterator, it will be faster to use NewIterator()
// and avoid the extra seek.
Iterator* GetIterator() const;

函数实现 implementation

在函数实现处描述函数的实现细节.

函数体 body

在函数体内, you should have comments in tricky, non-obvious, interesting, or important parts of your code,

  • tricks
// Divides result by two, taking into account that x
// contains the carry from the add.
for (int i = 0; i < result->size(); i++) {
  x = (x << 8) + (*result)[i];
  (*result)[i] = x >> 1;
  x &= 1;
}
  • non-obvious code
// If we have enough memory, mmap the data portion too.
mmap_budget = max<int64>(0, mmap_budget - index_->length());
if (mmap_budget >= data_size_ && !MmapData(mmap_chunk_bytes, mlock))
  return;  // Error already logged.

函数参数 parameters

当函数参数无法很好地 self-explain 时, 可以这样做

  • 参数是一个任意的常量, ok , 起个名字, 变成 const 常量.

  • bool 变量用 enum 变量代替, 直接甩一个 true/false 一脸懵逼, 写成 enum 带自解释就好了

  • 多个参数, 不如定义为一个class 或者 struct

  • 如果是个太长的表达式, 拜托, 还是起个变量名吧

  • 实在懒得动了, 那就在调用的时候用注释声明吧

  • Bad

> // What are these arguments?
const DecimalNumber product = CalculateProduct(values, 7, false, nullptr);
  • Good
ProductOptions options;
options.set_precision_decimals(7);
options.set_use_cache(ProductOptions::kDontUseCache);
const DecimalNumber product =
    CalculateProduct(values, options, /*completion_callback=*/nullptr);

TODO 注释

// TODO(aaa@qq.com): Use a "*" here for concatenation operator.
// TODO(Zeke) change this to use relations.
// TODO(bug 12345): remove the "Last visitors" feature

// TODO(bug 12345): Fix by November 2005
// TODO(bug 12345): Remove this code when all clients can handle XML responses

Formatting

Line Length

  • 每行最长 80 个 char

Non-ASCII Characters

  • Non-ASCII characters should be rare, and must use UTF-8 formatting.

Spaces vs. Tabs

  • 只用 Spaces,
  • 一次缩进 2 个
  • 设置编辑器, hit Tab 也 print spaces

Function Declarations and Definitions

  • 尽量一行, 多行对齐
  • Unused parameters that are obvious from context may be omitted, Foo(Foo&&)
  • Unused parameters that might not be obvious should comment out the variable name in the function definition, like void Circle::Rotate(double /*radians*/) {}

Lambda Expressions

  • Format parameters and bodies as for any other function, and capture lists like other comma-separated lists.
// For by-reference captures, do not leave a space between the ampersand (&) and the variable name.
int x = 0;
auto x_plus_n = [&x](int n) -> int { return x + n; }

// Short lambdas may be written inline as function arguments.

std::set<int> blacklist = {7, 8, 9};
std::vector<int> digits = {3, 9, 1, 8, 4, 7, 1};
digits.erase(std::remove_if(digits.begin(), digits.end(), [&blacklist](int i) {
               return blacklist.find(i) != blacklist.end();
             }),
             digits.end());

Function Calls

  • 尽量一行
  • 分行对齐
  • 重点突出, 可加注释

Braced Initializer List Format

  • 同 Function Calls

Conditionals

  • 短的, 一行搞定
if (condition) DoSomething();
  • 复杂的, 注意对齐
if (condition) {
  ...  //2 space indent 
} else if (...) {
  ...
} else {
  ...
}

Loops and Switch Statements

  • 一般 Switch 这样写
switch (var) {
  case 0: { // 2 space indent
    ...     // 4 space indent
    break;
  }
  case 1: {
    ...
    break;
  }
  default: {
    assert(false);
  }
}
  • Fall-through 的, 加 macro, ABSL_FALLTHROUGH_INTENDED defined in absl/base/macros.h
switch (x) {
  case 41:  // No annotation needed here.
  case 43:
    if (dont_be_picky) {
      // Use this instead of or along with annotations in comments.
      ABSL_FALLTHROUGH_INTENDED;
    } else {
      CloseButNoCigar();
      break;
    }
  case 42:
    DoSomethingSpecial();
    ABSL_FALLTHROUGH_INTENDED;
  default:
    DoSomethingGeneric();
    break;
}
  • 小 for-loop,
for(int i = 0; i < kSomeNumber; ++i)
	printf("I Love you \n");
  • Empty loop bodies 要注意格式, 不要被误解
while (condition) {
  // Repeat test until it returns false.
}
for (int i = 0; i < kSomeNumber; ++i) {}  // Good - one newline is also OK.
while (condition) continue;  // Good - continue indicates no logic.

while (condition);  // Bad - looks like part of do/while loop.

Pointer and Reference Expresions

// Good Forms
x = *p;
p = &x;
x = r.y;
x = r->y;

// Either is OK, keep it consistently
const string &str;
const string& str;
char *c;
char* c;

// Bad!
int x, *y; // Disallowed - no & or * in multiple declaration
char * c; // Disallowed
const string & str; //Disallowed

Boolean Expressions

  • Google style 逻辑符号后置, 我喜欢前置
if (this_one_thing > this_other_thing &&
    a_third_thing == a_fourth_thing &&
    yet_another && last_one) {
  ...
}

Return Values

return result;       // No parentheses in the simple case.

// Parentheses OK to make a complex expression more readable.
return (some_long_condition &&
        another_condition);

Variable and Array Initialization

int x = 3;
string name = "Some Name";

// Be careful
std::vector<int> v(100, 1);  // A vector containing 100 items: All 1s.
std::vector<int> v{100, 1};  // A vector containing 2 items: 100 and 1.
int pi(3.14);  // OK -- pi == 3.
int pi{3.14};  // Compile error: narrowing conversion.

Preprocessor Directives

  • #preprocessor 总是置于行首.
// Good - directives at beginning of line
  if (lopsided_score) {
#if DISASTER_PENDING      // Correct -- Starts at beginning of line
    DropEverything();
# if NOTIFY               // OK but not required -- Spaces after #
    NotifyClient();
# endif
#endif
    BackToNormal();
  }

Class Format

class MyClass : public OtherClass {
 public:      // Note the 1 space indent!
  MyClass();  // Regular 2 space indent.
  explicit MyClass(int var);
  ~MyClass() {}

  void SomeFunction();
  void SomeFunctionThatDoesNothing() {
  }

  void set_some_var(int var) { some_var_ = var; }
  int some_var() const { return some_var_; }

 private:
  bool SomeInternalFunction();

  int some_var_;
  int some_other_var_;
};

Constructor Initializer Lists

// When everything fits on one line:
MyClass::MyClass(int var) : some_var_(var) {
  DoSomething();
}

// If the signature and initializer list are not all on one line,
// you must wrap before the colon and indent 4 spaces:
MyClass::MyClass(int var)
    : some_var_(var), some_other_var_(var + 1) {
  DoSomething();
}

// When the list spans multiple lines, put each member on its own line
// and align them:
MyClass::MyClass(int var)
    : some_var_(var),             // 4 space indent
      some_other_var_(var + 1) {  // lined up
  DoSomething();
}

// As with any other code block, the close curly can be on the same
// line as the open curly, if it fits.
MyClass::MyClass(int var)
    : some_var_(var) {}

Namespace Formatting

  • The contents of namespaces are not indented.
namespace {

void foo() {  // Correct.  No extra indentation within namespace.
  ...
}

}  // namespace
  • When declaring nested namespaces, put each namespace on its own line
namespace foo {
namespace bar {

Horizontal Whitespace

  • Use of horizontal whitespace depends on location. Never put trailing whitespace at the end of a line.
// General
oid f(bool b) {  // Open braces should always have a space before them.
  ...
int i = 0;  // Semicolons usually have no space before them.
// Spaces inside braces for braced-init-list are optional.  If you use them,
// put them on both sides!
int x[] = { 0 };
int x[] = {0};

// Spaces around the colon in inheritance and initializer lists.
class Foo : public Bar {
 public:
  // For inline function implementations, put spaces between the braces
  // and the implementation itself.
  Foo(int b) : Bar(), baz_(b) {}  // No spaces inside empty braces.
  void Reset() { baz_ = 0; }  // Spaces separating braces from implementation.
  ...
  
  
// Loops and Conditionals
if (b) {          // Space after the keyword in conditions and loops.
} else {          // Spaces around else.
}
while (test) {}   // There is usually no space inside parentheses.
switch (i) {
for (int i = 0; i < 5; ++i) {

// Range-based for loops always have a space before and after the colon.
for (auto x : counts) {
  ...
}
switch (i) {
  case 1:         // No space before colon in a switch case.
    ...
  case 2: break;  // Use a space after a colon if there's code after it.
  
  
  
// Operators
// Assignment operators always have spaces around them.
x = 0;
// Other binary operators usually have spaces around them, but it's
// OK to remove spaces around factors.  Parentheses should have no
// internal padding.
v = w * x + y / z;
v = w*x + y/z;
v = w * (x + z);

// No spaces separating unary operators and their arguments.
x = -5;
++x;
if (x && !y)




// Templates and Casts
// No spaces inside the angle brackets (< and >), before
// <, or between >( in a cast
std::vector<string> x;
y = static_cast<char*>(x);

// Spaces between type and pointer are OK, but be consistent.
std::vector<char *> x;

Vertical Whitespace

  • Minimize use of vertical whitespace. The more code that fits on one screen, the easier it is to follow and understand the control flow of the program. Use whitespace purposefully to provide separation in that flow.
  • some rules
    • Blank lines at the beginning or end of a function do not help readability.
    • Blank lines inside a chain of if-else blocks may well help readability.
    • A blank line before a comment line usually helps readability

Exceptions to the Rules

Existing Non-conformant Code

Windows Code

  • Use the Google naming conventions,including the .cc extention for source file
  • 尽量少用 Windows 专属 特性, 尽量靠近标准 C++
  • 当使用 VC++ 编译的时候, 设置编译器 warning level 3 or higher, treat all warnings as errors. => 0 warnings, 0 errors
  • 不使用 #pragma once, 使用 include guard

Parting Words : Use common sense and BE CONSISTENT

III. 专题深入, 积沙成塔


Ref

相关标签: coding style