C++编程规范(参考Google、华为)

Huan Lee Lv5

文件名及版权信息

1、文件名

C++文件名全部都是小写,且单词之间用_分割,如:verilog_parse.cpp

2、版权信息

所有文件均需要统一格式的版权信息

头文件

1、在.h中使用#define来防止头文件被多重包含,并在最后注释出宏的名字

命名格式:<PROJECT>_<FILE>_H_

1
#ifndef TIMER_FLOW_H_#define TIMER_FLOW_H_...#endif

2、尽量避免使用前置声明,优先使用#include来包含相关头文件

包含头文件:

1
#include "timer.h"

前置声明:

1
class Timer;

3、#include的包含顺序

头文件的包含顺序为:当前.cpp文件直接关联的头文件、C库文件、C++库文件、其他项目的头文件、本项目中的其他头文件。

1
#include "timer.h"#include <stdio.h>#include <string>#include "verilog.h"#include "node.h"

4、头文件应向稳定的方向包含

头文件的包含关系是一种依赖,一般来说,有以下几种包含原则。

应当让不稳定的模块依赖稳定的模块,从而当不稳定的模块发生变化时,不会影响(编译)稳定的模块
禁止头文件循环依赖,指a.h包含b.h,b.h包含c.h,c.h包含a.h。
禁止包含用不到的头文件。
头文件应当自包含,指任意一个头文件均可独立编译。

作用域

1、在命名空间的最后注释出命名空间的名字

1
namespace mynamespace{...}

2、避免使用using引入整个命名空间的标识符号

1
using namespace mynamespace;

3、避免进行大量构造及析构操作

1
for (int i = 0; i < 10000; i++) {  Timer t;  t.DoSomething(i);}

建议使用以下代码:

1
Timer t;for (int i = 0; i < 10000; i++) {  t.DoSomething(i);}

4、尽量在变量声明时进行初始化,且避免进行无效的初始化

1
int i;i = f();int j = g();int k = 0;k = 0xff;

5、尽量避免全局函数和全局变量,使用命名空间或static关键字等进行作用域限制

6、禁止定义静态储存周期非POD变量

静态存储周期变量,即包括了全局变量、静态变量、静态类成员变量和函数静态变量,都必须是原生数据类型(POD:Plain Old Data):即intcharfloat,以及POD类型的指针、数组和结构体。

禁止使用类的静态存储周期变量,即禁用vectorstring等:由于构造和析构函数调用顺序的不确定性,它们会导致难以发现的bug。不过constexpr变量除外,因为它不涉及动态初始化和析构

1、不要在构造函数和析构函数中调用虚函数

如果在派生类的构造函数和析构函数中调用了虚函数,这类调用不会重定向到派生类的虚函数实现。

2、不要在无法报出错误时进行可能失败的初始化

如果代码允许,直接终止程序是一个合适的处理错误方式。否则,可以考虑用Init()方法或工厂函数。

3、建议使用初始化列表构造对象,且和构造函数、析构函数一样放在类外实现

使用初始化列表构造对象,比起构造函数的代码块初始化效率更高、性能更好。

1
class A{public:  A() : {};private:  int m_a;  B* m_b;};A::A() : m_a(0), m_b(nullptr) {}

4、不要定义隐式转换类型

对于转换运算符和单参数构造函数,建议使用explicit关键字。
举例如下:

1
class Things{public:  explicit Things(const std::string &name = "") :    m_name(name), m_height(0), m_weight(0) { }  int CompareTo(const Things &other);private:  std::string m_name;  int m_height;  int m_weight;};

这里的构造函数用explicit关键字来防止隐式类型转换。

1
Things a;std::string s = "book";int ret = a.CompareTo(s);int ret = a.CompareTo(Things(s));

5、拷贝构造函数和拷贝赋值运算符,移动构造函数和移动赋值运算符成套使用,如果不需要,则将它们显式地禁用

如果类型可拷贝,则需要同时给出拷贝构造函数和拷贝赋值运算符的定义。

1
MyClass(const MyClass &t){  if (this == &t) return;  if (t.text == nullptr) return;  int len = strlen(t.text);  text = new char[len + 1];  strcpy(text, t.text);}MyClass &operator = (const MyClass &t){  if (this == &t) return *this;  if (text != nullptr) {    free(text);    text = nullptr;  }  if (t.text == nullptr) return *this;  int len = strlen(t.text);  free(text);  text = new char[len + 1];  strcpy(text, t.text);  return *this;}

同理,如果类型可移动,则需要同时给出移动构造函数和移动赋值运算符的定义。

由于存在对象切割的风险,不要为任何有可能有派生类的对象提供赋值操作或者拷贝 / 移动构造函数(当然也不要继承有这样的成员函数的类)。如果你的基类需要可复制属性,请提供一个public virtual Clone()和一个protected的拷贝构造函数以供派生类实现。

如果你的类不需要拷贝 / 移动操作,请显式地通过在public域中使用= deleteDISALLOW_COPY_AND_ASSIGN禁用之。
方法一:

1
class MyClass{public:  MyClass(char *text);  ~MyClass();  MyClass(const MyClass &) = delete;  MyClass &operator = (const MyClass &) = delete;private:  char *m_text;}

方法二:

1
#define DISALLOW_COPY_AND_ASSIGN(MyClass) \  MyClass(const MyClass &); \  MyClass &operator = (const MyClass &)class MyClass{public:  MyClass(char *text);  ~MyClass();private:  DISALLOW_COPY_AND_ASSIGN(MyClass);  char *m_text;}

6、仅当只有数据成员时使用struct,其它一概使用class

7、优先考虑使用组合,其次是继承

不要过度使用继承,组合常常更合适一些。尽量做到只在“是一个”,而非“有一个”的情况下使用继承。

8、仅使用public继承

所有继承必须是public的,如果想使用私有继承,则应该替换成把基类的实例作为成员对象的方式。

9、如果类有虚函数,则析构函数也应该为虚函数

在声明重载时,请使用overridefinalvirtual的其中之一进行标记。

10、尽量不要重载运算符,也不要创建用户定义字面量

但不要为了避免重载操作符而走极端。比如,应当定义===<<,而不是Equals()CopyFrom()PrintTo()

11、将所有数据成员声明为private,除非是static const类型成员

这么做的原因是要求对数据成员进行存取控制。

12、将相似的声明放在一起,将public部分放在最前

类定义一般应以public:开始,后跟protected:,最后是private:
在各个部分中,建议将类似的声明放在一起,并且建议以如下的顺序:类型(包括typedefusing和嵌套的结构体与类)、变量、工厂函数、构造函数、赋值运算符、析构函数、其他函数、数据成员。

函数

1、编写简短、凝练、功能单一的函数,尽量不要写超过 100 行的函数

如果函数超过 100 行,可以考虑在不影响程序结构的前提下对其进行分割。

2、不同函数中的重复代码应该尽可能提炼成单一的函数

3、避免函数的代码嵌套过深,尽量低于 5 层

嵌套深度,指函数中的代码控制块(如ifforwhileswitch等)之间互相包含的深度。嵌套过深将导致代码的阅读成本增加。

4、函数的参数应该尽量不超过 5 个

5、废弃代码应及时清理

可以使用注释括起现在暂未使用而将来可能使用的代码,但废弃代码更应当被清除。

6、将所有输入参数放在所有输出参数之前

需要注意的是,在加入新参数时不要因为它们是新参数就置于参数列表最后,而是仍然要按照该规则,即将新的输入参数也置于输出参数之前。

7、所有按引用传递的参数必须加上const

函数参数列表中,所有引用参数都必须是const。如果一个参数有被改变的可能,则建议使用指针。

8、尽量使用const对声明的变量或参数进行限制

尽量使用const,将会提升代码的健壮性。

在类的set方法中,当输入参数为简单类型,则不需要添加const;当输入参数为容器时,使用const &可提高性能。

1
void set_float(float var);void set_string(const std::string &str);void set_things(const std::vector<std::string> &vec);

在类的get方法中,其后加const表明该函数为只读函数;当输出参数为容器时,使用const &可提高性能,且此时返回值不可被修改。

1
float get_float() const;const std::vector<std::string> &get_things() const;

9、只允许在非虚函数中使用缺省参数,且必须保证缺省参数的值始终一致

对于虚函数,不允许使用缺省参数,因为在虚函数中缺省参数不一定能正常工作。如果在每个调用点缺省参数的值都有可能不同,在这种情况下缺省参数也不允许使用。

1
void Func(int n = counter++);

10、避免野指针的产生

指针变量在声明时,就应该进行初始化赋值(nullptr或准确的地址,不建议使用NULL)。
指针在释放后,且生命周期暂未终结时,需要置空(nullptr,不建议使用NULL)。

11、建议使用auto绕过繁琐的类型名,且仅在局部变量使用

12、内联函数应该尽量短

关键字inline必须与函数定义放在一起才能使函数成为内联函数,仅仅将inline放在函数声明前面不起任何作用
内联函数应该尽可能短,原则上不允许超过 10 行的内联函数。

命名

在了解命名规则前,首先需要了解几种命名法。

小驼峰命名法
命名的第一个单词以小写字母开始,第二个单词开始以后的每个单词的首字母都采用大写字母。且单词之间无下划线连接,如firstNamelastName。这样的命名看上去就像骆驼峰一样此起彼伏,故得名。

大驼峰命名法
又名“帕斯卡命名法”,与小驼峰命名法类似,但每个单词的首字母均大写。如FirstNameLastName

匈牙利命名法
基本结构是:属性 + 类型 + 具体描述。如uiNumpTimerstrNameg_pSource
属性部分:

text
1
2
3
4
5
g_ 全局变量
c_ 常量
m_ 类成员变量
s_ 静态变量

类型部分:

text
1
2
3
4
5
6
7
8
9
10
11
12
c char
b bool
i/n int
u unsigned
f float/file
d double
l long
h handle
fn 函数
p 指针
str string

下划线命名法
命名的每个单词用下划线隔开,且每个单词均小写。如first_namelast_name

1、尽可能使用描述性的命名,不要使用含糊不清的缩写

2、所有类型名称使用大驼峰命名法

所有类型包括:类、结构体、类型定义(typedef)、枚举、类型模板参数。

1
class UrlTable{ ...struct UrlTableProperties{ ...typedef hash_map<UrlTableProperties *, string> PropertiesMap;using PropertiesMap = hash_map<UrlTableProperties *, string>;enum UrlTableErrors{ ...

3、变量使用下划线命名法,且类的成员变量以m_开头

1
string table_name;class TableInfo{  ...private:  string m_table_name;};struct UrlTableProperties{  string table_name;};

4、常量使用小驼峰命名法,且以k开头

常量,即声明为constexprconst的变量。

1
const int kDaysInAWeek = 7;

5、函数使用大驼峰命名法或下划线命名法

一般来说,相对于一个功能模块而言,模块外有可能会调用到的函数(即对外API),命名应该使用大驼峰命名法。而模块内部调用的函数,命名应该使用下划线命名法。

6、命名空间使用下划线命名法

1
namespace gbtimer_verilog{ ...

7、宏的命名使用全大写,并且可以使用下划线

1
#define PI_ROUNDED 3.0

8、枚举的命名应当和常量或宏一致,且枚举的第一个值应当为默认值或无效值

具体使用哪种,视项目内已有代码而定。

1
enum UrlTableErrors{    kOK = 0,    kErrorOutOfMemory,    kErrorMalformedInput,};enum AlternateUrlTableErrors{    OK = 0,    OUT_OF_MEMORY = 1,    MALFORMED_INPUT = 2,};

注释

1、函数声明处的注释描述函数功能,定义处的注释描述函数实现

但同时也要避免对显而易见的内容进行注释

2、对那些临时的、短期的解决方案,或需要补充的代码使用TODO注释


格式

每个人都可能有自己的代码风格和格式,但如果一个项目中的所有人都遵循同一风格的话,这个项目就能更顺利地进行。每个人未必能同意下述的每一处格式规则,而且其中的不少规则需要一定时间的适应,但整个项目服从统一的编程风格是很重要的,只有这样才能让所有人轻松地阅读和理解代码。

1、每一行代码字符数尽量不要超过 80

2、避免产生超过 3000 行的超大文件

3、使用空格缩进,不要使用制表符,且每级缩进为 2 个空格

4、相对独立的程序块之间建议添加空行

5、返回类型和函数名在同一行,参数也尽量放在同一行,如果放不下就对形参分行

1
ReturnType ClassName::ReallyLongFunctionName(Type par_name1,  Type par_name2, Type par_name3){  DoSomething();  ...}

或者:

1
ReturnType ClassName::ReallyLongFunctionName(Type par_name1,                                             Type par_name2,                                             Type par_name3){  DoSomething();  ...}

6、iffordowhileswitchcasedefault等语句与{在同一行,而}独占一行

1
if (condition) {  ...} else if (...) {  ...} else {  ...}if (x == kFoo) return new Foo();
1
switch (var) {  case 0: {    ...    break;  }  case 1: {    ...    break;  }  default: {    ...  }}
1
while (condition) {}for (int i = 0; i < kSomeNumber; ++i) {  ...}int i = 0;for ( ; i < kSomeNumber; ) {  ...  ++i;}

7、>.、指针/地址操作符``与&前后不加空格

1
x = *p;p = &x;x = r.y;x = r->y;

8、在声明指针变量或参数时,建议``与&紧挨变量名

1
char *c;const string &str;char* c;const string& str;

9、一个表达式换行时,操作符总位于行尾

1
if (this_one_thing > this_other_thing &&    a_third_thing == a_fourth_thing &&    yet_another && last_one) {  ...}

10、不要在return表达式里加上非必须的括号

1
return result;return (result);return (some_long_condition && another_condition);

11、预处理指令不要缩进,从行首开始

1
if (lopsided_score) {#if DISASTER_PENDING    DropEverything();#endif    BackToNormal();  }

12、访问控制快的声明依次序是public:protected:private,且不进行缩进

1
class MyClass : public OtherClass{public:  MyClass();  explicit MyClass(int var);  ~MyClass() {}  void SomeFunction();  int get_var() const { return m_var; }private:  int m_var;};

13、命名空间、函数、类、结构体、联合体的{}均另起一行,枚举视长短而定

1
struct config_rule{  char *name;  union  {    int i;    double d;  };};enum config_type{  CONFIG_TYPE1,  CONFIG_TYPE2,  ...};enum num_type { NUM_TYPE1, NUM_TYPE2 };

14、水平留白

水平留白使用根据在代码中的位置决定,但永远不要在行尾添加没意义的留白。

1
int x[] = { 0 };int x[] = {0};x++;v = w * (x + z);x = a > b ? a : b;vector<string> x;y = static_cast<char *>(x);void func(int a, int b);

15、垂直留白

基本原则是:同一屏可以显示的代码越多,越容易理解程序的控制流。当然,过于密集的代码块和过于疏松的代码块同样难看,这取决于你的判断。但通常是垂直留白越少越好,且函数体首尾不要留空行,不要有连续的空行

编译

1、程序编译时产生的所有warning都应该被修复

特殊的,LEX与YACC产生的移进-归约冲突及归约-归约冲突视情况而定,但原则上所有告警都应该被修复。

  • Title: C++编程规范(参考Google、华为)
  • Author: Huan Lee
  • Created at : 2023-05-28 16:16:44
  • Updated at : 2024-02-26 04:53:15
  • Link: https://www.mirthfullee.com/2023/05/28/C++编程规范(参考Google、华为)/
  • License: This work is licensed under CC BY-NC-SA 4.0.
On this page
C++编程规范(参考Google、华为)