Google C++ Coding Style 学习笔记

写在前面:最新公司马上就要开始开发一款视觉产品,工程量较大,且需要对客户提供可以二次开

发的SDK,整个项目用C++编写。

这就对代码质量提出了非常高的要求,同时,如何设计出优雅稳定的API也是相当大的挑战。

当然,团队首先需要解决的问题是编程规范的确立。之前,公司规模较小,对C++代码规范不够重视,导致各个C++项目代码质量参差不齐,标准不一。

所以,公司领导希望借此视觉项目的机会,建立起公司C++编码规范,统一指导后续c++开发项目。建立编码规范本身也是对C++语言的再学习过程,利于提高整个团队对C++的理解和使用。

经过简单调研,我们决定使用Google C++ Coding Style。作为项目主要开发人员,现将学习的心得记录如下,注意,这里不是对Google C++ Coding Style的全文翻译,而是捕捉和记录一些可能遗漏的要点。

1、Headers

1.1 self-contained headers

Header files should be self-contained (compile on their own)

Prefer placing the definitions for template and inline functions in the same file as their declarations. The definitions of these constructs must be included into every .cc file that uses them, or the program may fail to link in some build configurations.

As an exception, a template that is explicitly instantiated for all relevant sets of template arguments, or that is a private implementation detail of a class, is allowed to be defined in the one and only .cc file that instantiates the template.

(推荐将模板类和内敛函数的声明和定义放在一个头文件里面,为什么?下面的文章可以说明。

When the compiler encounters a declaration of a TestTemp object of some specific type, e.g., int, it must have access to the template implementation source. Otherwise, it will have no idea how to construct the TestTemp member functions. And, if you have put the implementation in a source (TestTemp.cpp) file and made it a separate part of the project, the compiler will not be able to find it when it is trying to compile the client source file. And, #includeing the header file (TestTemp.h) will not be sufficient at that time. That only tells the compiler how to allocate for the object data and how to build the calls to the member functions, not how to build the member functions. And again, the compiler won't complain. It will assume that these functions are provided elsewhere, and leave it to the linker to find them. So, when it's time to link, you will get "unresolved references" to any of the class member functions that are not defined "inline" in the class definition.)

1.2 The #define guard

<PROJECT>_<PATH>_<FILE>_H_

(主要是命名格式问题)

1.3 Forward Declaration

Avoid using forward declarations where possible. Just #include the headers you need.

(尽量避免使用前置声明,虽然前置声明可以减少编译时间,避免不需要的重编译,但是还是可能会规避掉必要的重新编译,无法在后续对头文件API进行更改,还会隐藏类与类之间的继承关系导致多态失效等等,总之,不要用前置声明)

1.4 Inline Functions

Define functions inline only when they are small, say, 10 lines or fewer.

Another useful rule of thumb: it's typically not cost effective to inline functions with loops or switch statements

(只在代码行数少于10行的情况下使用内联函数,递归函数不要使用内联函数,函数内有循环或者switch不要使用内联函数,虚函数可以使用内敛,但是更多是为了方便或者生成文档,虽然编译器不一定会将它作为内联函数处理)

1.5 Names and Orders of Includes

  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.

(头文件需要放在project文件名下,即project/dir1/a.h,不要使用UNIX规范下的./..等相对路径。

至于为什么要把该实现文件的头文件放在第一个,原因如下:

Including the .h file as the very first line of the .c file ensures that no critical piece of information intrinsic to the physical interface of the component is missing from the .h file (or, if there is, that you will find out about it as soon as you try to compile the .c file)

它能确保头文件包含的内容完整,这样,如果头文件有问题,那么实现这个类的人将首先碰到编译问题,而不是留给其他使用这个类的人。记住,不要指望通过include某个头文件来顺带引入另个需要的头文件,之前说过,每个头文件都要保持self-contained,直接引入你需要的那个header就好。)

2. Scoping

2.1 NameSpace

名称空间主要是用来避免名称冲突。有几点需要注意的,一是不要往std空间引入其他任何的名称,二是不要通过使用using来引入一个名称空间下所有的名称,但你可以引入某个具体的名称,三是如果要对名称空间使用别名,请在局部名称空间或局部函数,即局部作用域使用别名,不要在*空间使用别名,否则将会成为对外公开API的一部分,最后就是不要使用内联名称空间。

2.2 Unamed namespaces and Static variables

未命名的名称空间和静态变量声明可以限制变量或者函数的作用域为本文件,这样就可以在不同的文件中的最外层作用域定义同名变量而不会出现名称冲突,不需要外部链接的函数或者变量都可以放到未命名名称空间里面。internal link的解释可以参见https://*.com/questions/1358400/what-is-external-linkage-and-internal-linkage

2.3 Nonmember, static member and Global functions

非成员函数放到名称空间里面,少用全局函数。不要将所有的静态方法集中组合到一个单独的类中,说白了,哪怕是静态方法,你也得放在一个与之相关的普通的类中,静态不代表就应该全部独立出来。如果只需要本文件可见,放入未命名名称空间里。

2.4 Local variables

局部变量尽量在靠近第一次使用的地方声明,并及时初始化。

2.4 Static and Global variables

这个问题相对比较复杂,涉及到很多c++11&14的新特性新概念。

首先关于什么是trivally destructable: More formally it means that the type has no user-defined or virtual destructor and that all bases and non-static members are trivially destructible. 说白了,就是没有复杂的析构函数

All objects with static storage duration are destroyed at program exit (which happens before unjoined threads are terminated. )

关于多线程的情形,静态变量的生存周期结束的时刻是要早于unjoined线程被结束的时刻的。也就是说,unjoined线程可能访问到一个已经被析构的静态对象从而导致出错。

静态对象的初始化分为动态初始化和静态初始化,静态初始化即我们常说的自动置零,而动态初始化则涉及其他的操作,例如调用一个构造函数等,相对复杂。不同translation unit中的静态对象的生存周期的起始点没有一定先后顺序。所以就可能导致引用一个尚未初始化的静态对象。析构的顺序同样不能保证,unjoined线程最终可能试图访问一个已经被析构的静态对象。

所以,只有对象是trivally-destructible的时候,才能被用作全局或静态,因为trivally-desctructible不受析构的顺序的影响。constexpr因为是编译期确定,所以也算作简单可析构。

从初始化角度来看,只要不是常量初始化(constant initialization)那么其初始化顺序就无法保证,因此 Dynamic initialization of nonlocal variables is discouraged, and in general it is forbidden. 除非保证该变量的初始化与其他变量的初始化顺序无关(或者说其他静态变量的初始化不会引用到这个变量)

局部静态变量的动态初始化是OK的,不受上述规则影响。

啰里啰嗦说了那么多,总结起来就是,静态变量初始和析构顺序不确定,所以,对于这类变量,只用常量初始化,解除对顺序的依赖。析构也保证要trival。

2.5 thread-local variables

局部线程变量的生存期类似于静态变量,但是每个线程都有一个独立的拷贝,变量生成于线程创建,卒于线程结束。

类似于静态变量,局部线程变量也会面临初始化顺序问题(除非在函数体内声明),但不会面临析构顺序问题。

如果要在外层声明,请对变量进行常量初始化。

(未完待续……)

上一篇:HDUOJ-----2852 KiKi's K-Number(树状数组+二分)


下一篇:Google coding Style Guide : Google 编码风格/代码风格 手册/指南