diff --git a/zh-cn/contribute/OpenHarmony-cpp-coding-style-guide.md b/zh-cn/contribute/OpenHarmony-cpp-coding-style-guide.md index d45ce8cea608cd91b59d322f962e7e6211f7c498..6cdb3adfc3b74f1799105112407f875d0694bd0c 100755 --- a/zh-cn/contribute/OpenHarmony-cpp-coding-style-guide.md +++ b/zh-cn/contribute/OpenHarmony-cpp-coding-style-guide.md @@ -1,14 +1,14 @@ # C++语言编程规范 ## 目的 -规则并不是完美的,通过禁止在特定情况下有用的特性,可能会对代码实现造成影响。但是我们制定规则的目的“为了大多数程序员可以得到更多的好处”, 如果在团队运作中认为某个规则无法遵循,希望可以共同改进该规则。 -参考该规范之前,希望您具有相应的C++语言基础能力,而不是通过该文档来学习C++语言。 +规则并不是完美的,通过禁止在特定情况下有用的特性,可能会对代码实现造成影响。但是我们制定规则的目的“为了大多数程序员可以得到更多的好处”, 如果在团队运作中认为某个规则无法遵循,希望可以共同改进该规则。 +参考该规范之前,希望您具有相应的C++语言基础能力,而不是通过该文档来学习C++语言。 1. 了解C++语言的ISO标准; 2. 熟知C++语言的基本语言特性,包括C++ 03/11/14/17相关特性; 3. 了解C++语言的标准库; ## 总体原则 -代码需要在保证功能正确的前提下,满足**可读、可维护、安全、可靠、可测试、高效、可移植**的特征要求。 +代码需要在保证功能正确的前提下,满足**可读、可维护、安全、可靠、可测试、高效、可移植**的特征要求。 ## 重点关注 1. 约定C++语言的编程风格,比如命名,排版等。 @@ -31,8 +31,8 @@ 在不违背总体原则,经过充分考虑,有充足的理由的前提下,可以适当违背规范中约定。 例外破坏了代码的一致性,请尽量避免。'规则'的例外应该是极少的。 -下列情况,应风格一致性原则优先: -**修改外部开源代码、第三方代码时,应该遵守开源代码、第三方代码已有规范,保持风格统一。** +下列情况,应风格一致性原则优先: +**修改外部开源代码、第三方代码时,应该遵守开源代码、第三方代码已有规范,保持风格统一。** # 2 命名 ## 通用命名 @@ -112,11 +112,11 @@ enum UrlTableErrors { ... ```cpp // namespace namespace OsUtils { - + namespace FileUtils { - + } - + } ``` @@ -153,7 +153,7 @@ int g_activeConnectCount; void Func() { - static int packetCount = 0; + static int packetCount = 0; ... } ``` @@ -253,7 +253,7 @@ int Foo(int a) class MyClass { public: MyClass() : value_(0) {} - + private: int value_; }; @@ -305,7 +305,7 @@ ReturnType result = FunctionName(paramName1, paramName2, // Good:保持与上方参数对齐 paramName3); -ReturnType result = FunctionName(paramName1, paramName2, +ReturnType result = FunctionName(paramName1, paramName2, paramName3, paramName4, paramName5); // Good:参数换行,4 空格缩进 ReturnType result = VeryVeryVeryLongFunctionName( // 行宽不满足第1个参数,直接换行 @@ -340,7 +340,7 @@ if (objectIsNotExist) { // Good:单行条件语句也加大括号 如下是正确的写法: ```cpp -if (someConditions) { +if (someConditions) { DoSomething(); ... } else { // Good: else 与 if 在不同行 @@ -507,40 +507,40 @@ int&p = i; // Bad 编译预处理的"#"统一放在行首,即使编译预处理的代码是嵌入在函数体中的,"#"也应该放在行首。 ### 规则3.13.2 避免使用宏 -宏会忽略作用域,类型系统以及各种规则,容易引发问题。应尽量避免使用宏定义,如果必须使用宏,要保证证宏名的唯一性。 -在C++中,有许多方式来避免使用宏: -- 用const或enum定义易于理解的常量 -- 用namespace避免名字冲突 -- 用inline函数避免函数调用的开销 -- 用template函数来处理多种类型 +宏会忽略作用域,类型系统以及各种规则,容易引发问题。应尽量避免使用宏定义,如果必须使用宏,要保证证宏名的唯一性。 +在C++中,有许多方式来避免使用宏: +- 用const或enum定义易于理解的常量 +- 用namespace避免名字冲突 +- 用inline函数避免函数调用的开销 +- 用template函数来处理多种类型 在文件头保护宏、条件编译、日志记录等必要场景中可以使用宏。 ### 规则3.13.3 禁止使用宏来表示常量 -宏是简单的文本替换,在预处理阶段完成,运行报错时直接报相应的值;跟踪调试时也是显示值,而不是宏名; 宏没有类型检查,不宏全; 宏没有作用域。 +宏是简单的文本替换,在预处理阶段完成,运行报错时直接报相应的值;跟踪调试时也是显示值,而不是宏名; 宏没有类型检查,不安全; 宏没有作用域。 ### 规则3.13.4 禁止使用函数式宏 宏义函数式宏前,应考虑能否用函数替代。对于可替代场景,建议用函数替代宏。 -函数式宏的缺点如下: -- 函数式宏缺乏类型检查,不如函数调用检查严格 -- 宏展开时宏参数不求值,可能会产生非预期结果 -- 宏没有独产的作用域 +函数式宏的缺点如下: +- 函数式宏缺乏类型检查,不如函数调用检查严格 +- 宏展开时宏参数不求值,可能会产生非预期结果 +- 宏没有独立的作用域 - 宏的技巧性太强,例如#的用法和无处不在的括号,影响可读性 -- 在特定场景中必须用编译器对宏的扩展语法,如GCC的statement expression,影响可移植性 -- 宏在预编译阶段展开后,在期后编译、链接和调试时都不可见;而且包含多行的宏会展开为一行。函数式宏难以调试、难以打断点,不利于定位问题 -- 对于包含大量语句的宏,在每个调用点都要展开。如果调用点很多,会造成代码空间的膨胀 +- 在特定场景中必须用编译器对宏的扩展语法,如GCC的statement expression,影响可移植性 +- 宏在预编译阶段展开后,在期后编译、链接和调试时都不可见;而且包含多行的宏会展开为一行。函数式宏难以调试、难以打断点,不利于定位问题 +- 对于包含大量语句的宏,在每个调用点都要展开。如果调用点很多,会造成代码空间的膨胀 函数没有宏的上述缺点。但是,函数相比宏,最大的劣势是执行效率不高(增加函数调用的开销和编译器优化的难度)。 为此,可以在必要时使用内联函数。内联函数跟宏类似,也是在调用点展开。不同之处在于内联函数是在编译时展开。 - -内联函数兼具函数和宏的优点: -- 内联函数执行严格的类型检查 + +内联函数兼具函数和宏的优点: +- 内联函数执行严格的类型检查 - 内联函数的参数求值只会进行一次 - 内联函数就地展开,没有函数调用的开销 -- 内联函数比函数优化得更好 -对于性能要求高的产品代码,可以考虑用内联函数代替函数。 +- 内联函数比函数优化得更好 +对于性能要求高的产品代码,可以考虑用内联函数代替函数。 -例外: -在日志记录场景中,需要通过函数式宏保持调用点的文件名(__FILE__)、行号(__LINE__)等信息。 +例外: +在日志记录场景中,需要通过函数式宏保持调用点的文件名(__FILE__)、行号(__LINE__)等信息。 ## 空格和空行 ### 规则3.14.1 水平空格应该突出关键字和重要信息,避免不必要的留白 @@ -647,19 +647,19 @@ int MyClass::GetValue() const {} // Good: 对于成员函数定义,不要留 // Good: 类的派生需要留有空格 class Sub : public Base { - + }; // 构造函数初始化列表需要留有空格 -MyClass::MyClass(int var) : someVar_(var) +MyClass::MyClass(int var) : someVar_(var) { DoSomething(); } // 位域表示也留有空格 struct XX { - char a : 4; - char b : 5; + char a : 4; + char b : 5; char c : 4; }; ``` @@ -733,7 +733,7 @@ public: // 注意没有缩进 ~MyClass() {} void SomeFunction(); - void SomeFunctionThatDoesNothing() + void SomeFunctionThatDoesNothing() { } @@ -754,7 +754,7 @@ private: ### 规则3.15.2 构造函数初始化列表放在同一行或按四格缩进并排多行 ```cpp // 如果所有变量能放在同一行: -MyClass::MyClass(int var) : someVar_(var) +MyClass::MyClass(int var) : someVar_(var) { DoSomething(); } @@ -770,28 +770,28 @@ MyClass::MyClass(int var) // 如果初始化列表需要置于多行, 需要逐行对齐 MyClass::MyClass(int var) : someVar_(var), // 缩进4个空格 - someOtherVar_(var + 1) -{ + someOtherVar_(var + 1) +{ DoSomething(); } ``` # 4 注释 -一般的,尽量通过清晰的架构逻辑,好的符号命名来提高代码可读性;需要的时候,才辅以注释说明。 +一般的,尽量通过清晰的架构逻辑,好的符号命名来提高代码可读性;需要的时候,才辅以注释说明。 注释是为了帮助阅读者快速读懂代码,所以要从读者的角度出发,**按需注释**。 注释内容要简洁、明了、无二义性,信息全面且不冗余。 -**注释跟代码一样重要。** -写注释时要换位思考,用注释去表达此时读者真正需要的信息。在代码的功能、意图层次上进行注释,即注释解释代码难以表达的意图,不要重复代码信息。 +**注释跟代码一样重要。** +写注释时要换位思考,用注释去表达此时读者真正需要的信息。在代码的功能、意图层次上进行注释,即注释解释代码难以表达的意图,不要重复代码信息。 修改代码时,也要保证其相关注释的一致性。只改代码,不改注释是一种不文明行为,破坏了代码与注释的一致性,让阅读者迷惑、费解,甚至误解。 -使用英文进行注释。 +使用英文进行注释。 ## 注释风格 -在 C++ 代码中,使用 `/*` `*/`和 `//` 都是可以的。 -按注释的目的和位置,注释可分为不同的类型,如文件头注释、函数头注释、代码注释等等; +在 C++ 代码中,使用 `/*` `*/`和 `//` 都是可以的。 +按注释的目的和位置,注释可分为不同的类型,如文件头注释、函数头注释、代码注释等等; 同一类型的注释应该保持统一的风格。 注意:本文示例代码中,大量使用 '//' 后置注释只是为了更精确的描述问题,并不代表这种注释风格更好。 @@ -948,7 +948,7 @@ const int ANOTHER_CONST = 200; /* 上下对齐时,与左侧代码保持间 示例: ```cpp -// Foo.h +// Foo.h #ifndef FOO_H #define FOO_H @@ -957,7 +957,7 @@ class Foo { public: Foo(); void Fun(); - + private: int value_; }; @@ -985,7 +985,7 @@ void Foo::Fun() ## 头文件依赖 ### 规则5.2.1 禁止头文件循环依赖 -头文件循环依赖,指 a.h 包含 b.h,b.h 包含 c.h,c.h 包含 a.h, 导致任何一个头文件修改,都导致所有包含了a.h/b.h/c.h的代码全部重新编译一遍。 +头文件循环依赖,指 a.h 包含 b.h,b.h 包含 c.h,c.h 包含 a.h, 导致任何一个头文件修改,都导致所有包含了a.h/b.h/c.h的代码全部重新编译一遍。 而如果是单向依赖,如a.h包含b.h,b.h包含c.h,而c.h不包含任何头文件,则修改a.h不会导致包含了b.h/c.h的源代码重新编译。 头文件循环依赖直接体现了架构设计上的不合理,可通过优化架构去避免。 @@ -1018,7 +1018,7 @@ void Foo::Fun() ```cpp extern int Fun(); // Bad: 通过extern的方式使用外部函数 -void Bar() +void Bar() { int i = Fun(); ... @@ -1027,7 +1027,7 @@ void Bar() // b.cpp内容 ```cpp -int Fun() +int Fun() { // Do something } @@ -1038,7 +1038,7 @@ int Fun() ```cpp #include "b.h" // Good: 通过包含头文件的方式使用其他.cpp提供的接口 -void Bar() +void Bar() { int i = Fun(); ... @@ -1052,7 +1052,7 @@ int Fun(); // b.cpp内容 ```cpp -int Fun() +int Fun() { // Do something } @@ -1150,7 +1150,7 @@ namespace { void Foo::Fun() { int i = MAX_COUNT; - + InternalFun(); } @@ -1177,14 +1177,14 @@ using namespace NamespaceB; void G() { - Fun(1); + Fun(1); } ``` ```cpp // 源代码a.cpp #include "a.h" -using namespace NamespaceA; +using namespace NamespaceA; #include "b.h" void main() @@ -1267,12 +1267,12 @@ public: static Counter counter; return counter; } // 单实例实现简单举例 - - void Increase() + + void Increase() { value_++; } - + void Print() const { std::cout << value_ << std::endl; @@ -1305,7 +1305,7 @@ Counter::GetInstance().Print(); 构造,拷贝,移动和析构函数提供了对象的生命周期管理方法: - 构造函数(constructor): `X()` - 拷贝构造函数(copy constructor):`X(const X&)` -- 拷贝赋值操作符(copy assignment):`operator=(const X&)` +- 拷贝赋值操作符(copy assignment):`operator=(const X&)` - 移动构造函数(move constructor):`X(X&&)` *C++11以后提供* - 移动赋值操作符(move assignment):`operator=(X&&)` *C++11以后提供* - 析构函数(destructor):`~X()` @@ -1365,7 +1365,7 @@ public: { msgBuffer_ = nullptr; // Bad,不推荐在构造函数中赋值 } - + private: unsigned int msgID_{0}; // Good,C++11中使用 unsigned int msgLength_; @@ -1414,7 +1414,7 @@ private: Foo& operator=(const Foo&); }; ``` -2. 使用C++11提供的delete, 请参见后面现代C++的相关章节。 +2. 使用C++11提供的delete, 请参见后面现代C++的相关章节。 3. 推荐继承NoCopyable、NoMovable,禁止使用DISALLOW_COPY_AND_MOVE,DISALLOW_COPY,DISALLOW_MOVE等宏。 @@ -1502,8 +1502,8 @@ public: 示例:类Base是基类,Sub是派生类 ```cpp -class Base { -public: +class Base { +public: Base(); virtual void Log() = 0; // 不同的派生类调用不同的日志文件 }; @@ -1511,11 +1511,11 @@ public: Base::Base() // 基类构造函数 { Log(); // 调用虚函数Log -} +} -class Sub : public Base { +class Sub : public Base { public: - virtual void Log(); + virtual void Log(); }; ``` @@ -1525,13 +1525,13 @@ public: 同样的道理也适用于析构函数。 ### 规则7.1.7 多态基类中的拷贝构造函数、拷贝赋值操作符、移动构造函数、移动赋值操作符必须为非public函数或者为delete函数 -如果报一个派生类对象直接赋值给基类对象,会发生切片,只拷贝或者移动了基类部分,损害了多态行为。 +如果报一个派生类对象直接赋值给基类对象,会发生切片,只拷贝或者移动了基类部分,损害了多态行为。 【反例】 如下代码中,基类没有定义拷贝构造函数或拷贝赋值操作符,编译器会自动生成这两个特殊成员函数, 如果派生类对象赋值给基类对象时就发生切片。可以将此例中的拷贝构造函数和拷贝赋值操作符声明为delete,编译器可检查出此类赋值行为。 ```cpp -class Base { -public: +class Base { +public: Base() = default; virtual ~Base() = default; ... @@ -1565,8 +1565,8 @@ Foo(d); // 传入的是派生类对象 class Base { public: virtual std::string getVersion() = 0; - - ~Base() + + ~Base() { std::cout << "~Base" << std::endl; } @@ -1577,15 +1577,15 @@ public: class Sub : public Base { public: Sub() : numbers_(nullptr) - { + { } - + ~Sub() { delete[] numbers_; std::cout << "~Sub" << std::endl; } - + int Init() { const size_t numberCount = 100; @@ -1593,12 +1593,12 @@ public: if (numbers_ == nullptr) { return -1; } - + ... } - std::string getVersion() - { + std::string getVersion() + { return std::string("hello!"); } private: @@ -1616,7 +1616,7 @@ int main(int argc, char* args[]) } ``` 由于基类Base的析构函数没有声明为virtual,当对象被销毁时,只会调用基类的析构函数,不会调用派生类Sub的析构函数,导致内存泄漏。 -例外: +例外: NoCopyable、NoMovable这种没有任何行为,仅仅用来做标识符的类,可以不定义虚析构也不定义final。 ### 规则7.2.2 禁止虚函数使用缺省参数值 @@ -1629,7 +1629,7 @@ public: { std::cout << text << std::endl; } - + virtual ~Base(){} }; @@ -1637,9 +1637,9 @@ class Sub : public Base { public: virtual void Display(const std::string& text = "Sub!") { - std::cout << text << std::endl; + std::cout << text << std::endl; } - + virtual ~Sub(){} }; @@ -1647,12 +1647,12 @@ int main() { Base* base = new Sub(); Sub* sub = new Sub(); - + ... - + base->Display(); // 程序输出结果: Base! 而期望输出:Sub! sub->Display(); // 程序输出结果: Sub! - + delete base; delete sub; return 0; @@ -1669,15 +1669,15 @@ public: void Fun(); }; -class Sub : public Base { +class Sub : public Base { public: void Fun(); }; -Sub* sub = new Sub(); +Sub* sub = new Sub(); Base* base = sub; -sub->Fun(); // 调用子类的Fun +sub->Fun(); // 调用子类的Fun base->Fun(); // 调用父类的Fun //... @@ -1719,7 +1719,7 @@ class basic_istream {}; class basic_ostream {}; class basic_iostream : public basic_istream, public basic_ostream { - + }; ``` @@ -1894,7 +1894,7 @@ enum SessionState { ```cpp enum RTCPType { RTCP_SR = 200, - RTCP_MIN_TYPE = RTCP_SR, + RTCP_MIN_TYPE = RTCP_SR, RTCP_RR = 201, RTCP_SDES = 202, RTCP_BYE = 203, @@ -1904,7 +1904,7 @@ enum RTCPType { RTCP_XR = 207, RTCP_RSI = 208, RTCP_PUBPORTS = 209, - RTCP_MAX_TYPE = RTCP_PUBPORTS + RTCP_MAX_TYPE = RTCP_PUBPORTS }; ``` @@ -2006,7 +2006,7 @@ x = Func(i, i); 特例: 如果switch条件变量是枚举类型,并且 case 分支覆盖了所有取值,则加上default分支处理有些多余。 -现代编译器都具备检查是否在switch语句中遗漏了某些枚举值的case分支的能力,会有相应的warning提示。 +现代编译器都具备检查是否在switch语句中遗漏了某些枚举值的case分支的能力,会有相应的warning提示。 ```cpp enum Color { @@ -2031,11 +2031,11 @@ switch (color) { 应当按人的正常阅读、表达习惯,将常量放右边。写成如下方式: ```cpp if (value == MAX) { - + } if (value < MAX) { - + } ``` 也有特殊情况,如:`if (MIN < value && value < MAX)` 用来描述区间时,前半段是常量在左的。 @@ -2102,7 +2102,7 @@ C++提供的类型转换操作比C风格更有针对性,更易读,也更加 ```cpp // 不好的例子 -const int i = 1024; +const int i = 1024; int* p = const_cast(&i); *p = 2048; // 未定义行为 ``` @@ -2113,7 +2113,7 @@ class Foo { public: Foo() : i(3) {} - void Fun(int v) + void Fun(int v) { i = v; } @@ -2180,12 +2180,12 @@ public: { lock_.Acquire(); } - + ~LockGuard() { lock_.Release(); } - + private: LockType lock_; }; @@ -2199,7 +2199,7 @@ bool Update() } else { // 操作数据 } - + return true; } ``` @@ -2317,7 +2317,7 @@ class Foo { public: Foo(int length) : dataLength_(length) {} private: - const int dataLength_; + const int dataLength_; }; ``` @@ -2385,18 +2385,18 @@ C++提供了强大的泛型编程的机制,能够实现非常灵活简洁的 4. 模板如果使用不当,会导致运行时代码过度膨胀。 5. 模板代码难以修改和重构。模板的代码会在很多上下文里面扩展开来, 所以很难确认重构对所有的这些展开的代码有用。 -所以,OpenHarmony大部分部件禁止模板编程,仅有 __少数部件__ 可以使用泛型编程,并且开发的模板要有详细的注释。 -例外: +所以,OpenHarmony大部分部件禁止模板编程,仅有 __少数部件__ 可以使用泛型编程,并且开发的模板要有详细的注释。 +例外: 1. stl适配层可以使用模板 ## 宏 在C++语言中,我们强烈建议尽可能少使用复杂的宏 -- 对于常量定义,请按照前面章节所述,使用const或者枚举; +- 对于常量定义,请按照前面章节所述,使用const或者枚举; - 对于宏函数,尽可能简单,并且遵循下面的原则,并且优先使用内联函数,模板函数等进行替换。 ```cpp // 不推荐使用宏函数 -#define SQUARE(a, b) ((a) * (b)) +#define SQUARE(a, b) ((a) * (b)) // 请使用模板函数,内联函数等来替换。 template T Square(T a, T b) { return a * b; } @@ -2655,7 +2655,7 @@ void func() **理由** 智能指针会自动释放对象资源避免资源泄露,但会带额外的资源开销。如:智能指针自动生成的类、构造和析构的开销、内存占用多等。 -单例、类的成员等对象的所有权不会被多方持有的情况,仅在类析构中释放资源即可。不应该使用智能指针增加额外的开销。 +单例、类的成员等对象的所有权不会被多方持有的情况,仅在类析构中释放资源即可。不应该使用智能指针增加额外的开销。 **示例** @@ -2664,7 +2664,7 @@ class Foo; class Base { public: Base() {} - virtual ~Base() + virtual ~Base() { delete foo_; }