Google开源项目风格指南·重载某些运算符本身就是有害的,例如,重载一元运算符&会导致同样的代码有完全不同的含义这取决于重载的声明对某段代码而言是否是可见的,重载诸如&&,II和,会导致运算顺序和内建运算的顺序不一致·运算符从通常定义在类的外部,所以对于同一运算,可能出现不同的文件引入了不同的定义的风险如果两种定义都链接到同一二进制文件,就会导致未定义的行为,有可能表现为难以发现的运行时错误.·用户定义字面量所创建的语义形式对于某些有经验的C+十程序员来说都是很陌生的结论只有在意义明显,不会出现奇怪的行为并且与对应的内建运算符的行为一致时才定义重载运算符,例如1要作为位或或逻辑或来使用,而不是作为shell中的管道只有对用户自己定义的类型重载运算符更准确地说,将它们和它们所操作的类型定义在同一个头文件中,.cc中和命名空间中这样做无论类型在哪里都能够使用定义的运算符,并且最大程度上避免了多重定义的风险:如果可能的话,请避免将运算符定义为模板,因为此时它们必须对任何模板参数都能够作用,如果你定义了一个运算符,请将其相关且有意义的运算符都进行定义,并且保证这些定义的语义是一致的,例如,如果你重载了<,那么请将所有的比较运算符都进行重载,并且保证对于同一组参数,<和>不会同时返回true建议不要将不进行修改的二元运算符定义为成员函数,如果一个二元运算符被定义为类成员,这时隐式转换会作用域右侧的参数却不会作用于左侧:这时会出现a<b能够通过编译而b<a不能的情况,这是很让人迷惑的不要为了避免重载操作符而走极端:比如说,应当定义,=,和<<而不是Equals(),CopyFromO和PrintTo().反过来说,不要只是为了满足函数库需要而去定义运算符重载比如说,如果你的类型没有自然顺序,而你要将它们存人std::set中,最好还是定义一个自定义的比较运算符而不是重载<不要重载&&,Il,,或一元运算符&.不要重载operator"",也就是说,不要引入用户定义字面量类型转换运算符在隐式类型转换一节有提及。=运算符在可拷贝类型和可移动类型一节有提及。运算符<<在流一节有提及。同时请参见函数重载一节,其中提到的的规则对运算符重载同样适用2.4.93.9.存取控制总述将所有数据成员声明为private,除非是staticconst类型成员(遵循常量命名规则).出于技术上的原因,在使用GoogleTest时我们允许测试固件类中的数据成员为protected2.4.103.10.声明顺序总述将相似的声明放在一起,将public部分放在最前说明类定义一般应以public:开始,后跟protected:,最后是private:.省略空部分232.4.3.类
Google 开源项目风格指南 • 重载某些运算符本身就是有害的. 例如, 重载一元运算符 & 会导致同样的代码有完全不同的含义, 这取决于重载的声明对某段代码而言是否是可见的. 重载诸如 &&, || 和 , 会导致运算顺序和内建 运算的顺序不一致. • 运算符从通常定义在类的外部, 所以对于同一运算, 可能出现不同的文件引入了不同的定义的风险. 如果两种定义都链接到同一二进制文件, 就会导致未定义的行为, 有可能表现为难以发现的运行时 错误. • 用户定义字面量所创建的语义形式对于某些有经验的 C++ 程序员来说都是很陌生的. 结论 只有在意义明显, 不会出现奇怪的行为并且与对应的内建运算符的行为一致时才定义重载运算符. 例如, | 要作为位或或逻辑或来使用, 而不是作为 shell 中的管道. 只有对用户自己定义的类型重载运算符. 更准确地说, 将它们和它们所操作的类型定义在同一个头文件 中, .cc 中和命名空间中. 这样做无论类型在哪里都能够使用定义的运算符, 并且最大程度上避免了多重 定义的风险. 如果可能的话, 请避免将运算符定义为模板, 因为此时它们必须对任何模板参数都能够作 用. 如果你定义了一个运算符, 请将其相关且有意义的运算符都进行定义, 并且保证这些定义的语义是一 致的. 例如, 如果你重载了 <, 那么请将所有的比较运算符都进行重载, 并且保证对于同一组参数, < 和 > 不会同时返回 true. 建议不要将不进行修改的二元运算符定义为成员函数. 如果一个二元运算符被定义为类成员, 这时隐式转 换会作用域右侧的参数却不会作用于左侧. 这时会出现 a < b 能够通过编译而 b < a 不能的情况, 这是 很让人迷惑的. 不要为了避免重载操作符而走极端. 比如说, 应当定义 ==, =, 和 << 而不是 Equals(), CopyFrom() 和 PrintTo(). 反过来说, 不要只是为了满足函数库需要而去定义运算符重载. 比如说, 如果你的类型没有自 然顺序, 而你要将它们存入 std::set 中, 最好还是定义一个自定义的比较运算符而不是重载 <. 不要重载 &&, ||, , 或一元运算符 &. 不要重载 operator"", 也就是说, 不要引入用户定义字面量. 类型转换运算符在隐式类型转换 一节有提及. = 运算符在可拷贝类型和可移动类型 一节有提及. 运算符 << 在流 一节有提及. 同时请参见函数重载 一节, 其中提到的的规则对运算符重载同样适用. 2.4.9 3.9. 存取控制 总述 将 所有数据成员声明为 private, 除非是 static const 类型成员 (遵循常量命名规则). 出于技术上的 原因, 在使用 Google Test 时我们允许测试固件类中的数据成员为 protected. 2.4.10 3.10. 声明顺序 总述 将相似的声明放在一起, 将 public 部分放在最前. 说明 类定义一般应以 public: 开始, 后跟 protected:, 最后是 private:. 省略空部分. 2.4. 3. 类 23
Google开源项目风格指南在各个部分中,建议将类似的声明放在一起,并且建议以如下的顺序:类型(包括typedef,using和嵌套的结构体与类),常量,工厂函数,构造函数,赋值运算符,析构函数,其它函数,数据成员不要将大段的函数定义内联在类定义中通常,只有那些普通的,或性能关键且短小的函数可以内联在类定义中.参见内联函数一节.2.4.11译者(YuleFox)笔记1.不在构造函数中做太多逻辑相关的初始化2.编译器提供的默认构造函数不会对变量进行初始化,如果定义了其他构造函数,编译器不再提供,需要编码者自行提供默认构造函数;3.为避免隐式转换,需将单参数构造函数声明为explicit4.为避免拷贝构造函数,赋值操作的滥用和编译器自动生成,可将其声明为private且无需实现5.仅在作为数据集合时使用struct;6.组合>实现继承>接口继承>私有继承,子类重载的虚函数也要声明virtual关键字,虽然编译器允许不这样做;7.避免使用多重继承,使用时,除一个基类含有实现外,其他基类均为纯接口8.接口类类名以Interface为后缀,除提供带实现的虚析构函数,静态成员函数外,其他均为纯虚函数,不定义非静态数据成员,不提供构造函数,提供的话,声明为protected9.为降低复杂性,尽量不重载操作符,模板,标准类中使用时提供文档说明10.存取函数一般内联在头文件中;ll.声明次序:public->protected->private12.函数体尽量短小,紧凑,功能单一;2.54.函数2.5.14.1.参数顺序总述函数的参数顺序为:输入参数在先,后跟输出参数说明C/C十十中的函数参数或者是函数的输入,或者是函数的输出,或兼而有之,输人参数通常是值参或const引用,输出参数或输入/输出参数则一般为非const指针:在排列参数顺序时,将所有的输入参数置于输出参数之前:特别要注意,在加入新参数时不要因为它们是新参数就置于参数列表最后,而是仍然要按照前述的规则,即将新的输人参数也置于输出参数之前这并非一个硬性规定:输入/输出参数(通常是类或结构体)让这个问题变得复杂.并且,有时候为了其他函数保持一致,你可能不得不有所变通24Chapter2.C++风格指南-内容目录
Google 开源项目风格指南 在各个部分中, 建议将类似的声明放在一起, 并且建议以如下的顺序: 类型 (包括 typedef, using 和嵌套 的结构体与类), 常量, 工厂函数, 构造函数, 赋值运算符, 析构函数, 其它函数, 数据成员. 不要将大段的函数定义内联在类定义中. 通常,只有那些普通的, 或性能关键且短小的函数可以内联在类 定义中. 参见内联函数 一节. 2.4.11 译者 (YuleFox) 笔记 1. 不在构造函数中做太多逻辑相关的初始化; 2. 编译器提供的默认构造函数不会对变量进行初始化, 如果定义了其他构造函数, 编译器不再提供, 需 要编码者自行提供默认构造函数; 3. 为避免隐式转换, 需将单参数构造函数声明为 explicit; 4. 为避免拷贝构造函数, 赋值操作的滥用和编译器自动生成, 可将其声明为 private 且无需实现; 5. 仅在作为数据集合时使用 struct; 6. 组合 > 实现继承 > 接口继承 > 私有继承, 子类重载的虚函数也要声明 virtual 关键字, 虽然编译 器允许不这样做; 7. 避免使用多重继承, 使用时, 除一个基类含有实现外, 其他基类均为纯接口; 8. 接口类类名以 Interface 为后缀, 除提供带实现的虚析构函数, 静态成员函数外, 其他均为纯虚函 数, 不定义非静态数据成员, 不提供构造函数, 提供的话, 声明为 protected; 9. 为降低复杂性, 尽量不重载操作符, 模板, 标准类中使用时提供文档说明; 10. 存取函数一般内联在头文件中; 11. 声明次序: public -> protected -> private; 12. 函数体尽量短小, 紧凑, 功能单一; 2.5 4. 函数 2.5.1 4.1. 参数顺序 总述 函数的参数顺序为: 输入参数在先, 后跟输出参数. 说明 C/C++ 中的函数参数或者是函数的输入, 或者是函数的输出, 或兼而有之. 输入参数通常是值参或 const 引用, 输出参数或输入/输出参数则一般为非 const 指针. 在排列参数顺序时, 将所有的输入参 数置于输出参数之前. 特别要注意, 在加入新参数时不要因为它们是新参数就置于参数列表最后, 而是仍 然要按照前述的规则, 即将新的输入参数也置于输出参数之前. 这并非一个硬性规定. 输入/输出参数 (通常是类或结构体) 让这个问题变得复杂. 并且, 有时候为了其他 函数保持一致, 你可能不得不有所变通. 24 Chapter 2. C++ 风格指南 - 内容目录
Google开源项目风格指南2.5.24.2.编写简短函数总述我们倾向于编写简短,凝练的函数说明我们承认长函数有时是合理的,因此并不硬性限制函数的长度,如果函数超过40行,可以思索一下能不能在不影响程序结构的前提下对其进行分割即使一个长函数现在工作的非常好,一旦有人对其修改,有可能出现新的问题,甚至导致难以发现的bug使函数尽量简短,以便于他人阅读和修改代码在处理代码时,你可能会发现复杂的长函数:不要害怕修改现有代码:如果证实这些代码使用/调试起来很困难,或者你只需要使用其中的一小段代码,考虑将其分割为更加简短并易于管理的若干函数2.5.34.3.引用参数总述所有按引用传递的参数必须加上const定义在C语言中,如果函数需要修改变量的值,参数必须为指针,如intfoo(int*pval).在C++中,函数还可以声明为引用参数:intfoo(int&val)优点定义引用参数可以防止出现(*pval)++这样丑陋的代码.引用参数对于拷贝构造函数这样的应用也是必需的,同时也更明确地不接受空指针缺点容易引起误解,因为引用在语法上是值变量却拥有指针的语义结论函数参数列表中,所有引用参数都必须是const:void Foo(const string&in,string *out);事实上这在GoogleCode是一个硬性约定:输人参数是值参或const引用,输出参数为指针.输人参数可以是const指针,但决不能是非const的引用参数,除非特殊要求,比如swap()有时候,在输入形参中用constT*指针比constT&更明智:比如·可能会传递空指针·函数要把指针或对地址的引用赋值给输入形参总而言之,大多时候输入形参往往是constT&.若用constT*则说明输入另有处理.所以若要使用constT*,则应给出相应的理由,否则会使得读者感到迷惑252.5.4.函数
Google 开源项目风格指南 2.5.2 4.2. 编写简短函数 总述 我们倾向于编写简短, 凝练的函数. 说明 我们承认长函数有时是合理的, 因此并不硬性限制函数的长度. 如果函数超过 40 行, 可以思索一下能不 能在不影响程序结构的前提下对其进行分割. 即使一个长函数现在工作的非常好, 一旦有人对其修改, 有可能出现新的问题, 甚至导致难以发现的 bug. 使函数尽量简短, 以便于他人阅读和修改代码. 在处理代码时, 你可能会发现复杂的长函数. 不要害怕修改现有代码: 如果证实这些代码使用 / 调试起来 很困难, 或者你只需要使用其中的一小段代码, 考虑将其分割为更加简短并易于管理的若干函数. 2.5.3 4.3. 引用参数 总述 所有按引用传递的参数必须加上 const. 定义 在 C 语言中, 如果函数需要修改变量的值, 参数必须为指针, 如 int foo(int *pval). 在 C++ 中, 函数 还可以声明为引用参数: int foo(int &val). 优点 定义引用参数可以防止出现 (*pval)++ 这样丑陋的代码. 引用参数对于拷贝构造函数这样的应用也是必 需的. 同时也更明确地不接受空指针. 缺点 容易引起误解, 因为引用在语法上是值变量却拥有指针的语义. 结论 函数参数列表中, 所有引用参数都必须是 const: void Foo(const string &in, string *out); 事实上这在 Google Code 是一个硬性约定: 输入参数是值参或 const 引用, 输出参数为指针. 输入参数 可以是 const 指针, 但决不能是非 const 的引用参数, 除非特殊要求, 比如 swap(). 有时候, 在输入形参中用 const T* 指针比 const T& 更明智. 比如: • 可能会传递空指针. • 函数要把指针或对地址的引用赋值给输入形参. 总而言之, 大多时候输入形参往往是 const T&. 若用 const T* 则说明输入另有处理. 所以若要使用 const T*, 则应给出相应的理由, 否则会使得读者感到迷惑. 2.5. 4. 函数 25
Google开源项目风格指南2.5.44.4.函数重载总述若要使用函数重载,则必须能让读者一看调用点就胸有成竹,而不用花心思猜测调用的重载函数到底是哪一种这一规则也适用于构造函数定义你可以编写一个参数类型为conststring&的函数,然后用另一个参数类型为constchar*的函数对其进行重载:classMyclass(public:void Analyze(const string&text);void Analyze(const char *text, size_t textlen);J;优点通过重载参数不同的同名函数,可以令代码更加直观:模板化代码需要重载,这同时也能为使用者带来便利.缺点如果函数单靠不同的参数类型而重载(acgtyrant注:这意味着参数数量不变),读者就得十分熟悉C++五花八门的匹配规则,以了解匹配过程具体到底如何:另外,如果派生类只重载了某个函数的部分变体继承语义就容易令人困惑结论如果打算重载一个函数,可以试试改在函数名里加上参数信息例如,用Appendstring()和AppendInt()等,而不是一口气重载多个Append().如果重载函数的目的是为了支持不同数量的同一类型参数,则优先考虑使用std::vector以便使用者可以用列表初始化指定参数2.5.54.5.缺省参数总述只允许在非虚函数中使用缺省参数、必须保证缺省参数的值始终一致,缺省参数与函数重载遵循同样的规则。一般情况下建议使用函数重载,尤其是在缺省函数带来的可读性提升不能弥补下文中所提到的缺点的情况下优点有些函数一般情况下使用默认参数,但有时需要又使用非默认的参数,缺省参数为这样的情形提供了便利,使程序员不需要为了极少的例外情况编写大量的函数,和函数重载相比,缺省参数的语法更简洁明了减少了大量的样板代码,也更好地区别了“必要参数”和“可选参数”缺点缺省参数实际上是函数重载语义的另一种实现方式,因此所有不应当使用函数重载的理由也都适用于缺省参数26Chapter2.C++风格指南-内容目录
Google 开源项目风格指南 2.5.4 4.4. 函数重载 总述 若要使用函数重载, 则必须能让读者一看调用点就胸有成竹, 而不用花心思猜测调用的重载函数到底是哪 一种. 这一规则也适用于构造函数. 定义 你可以编写一个参数类型为 const string& 的函数, 然后用另一个参数类型为 const char* 的函数对 其进行重载: class MyClass { public: void Analyze(const string &text); void Analyze(const char *text, size_t textlen); }; 优点 通过重载参数不同的同名函数, 可以令代码更加直观. 模板化代码需要重载, 这同时也能为使用者带来便 利. 缺点 如果函数单靠不同的参数类型而重载 (acgtyrant 注:这意味着参数数量不变), 读者就得十分熟悉 C++ 五花八门的匹配规则, 以了解匹配过程具体到底如何. 另外, 如果派生类只重载了某个函数的部分变体, 继承语义就容易令人困惑. 结论 如果打算重载一个函数, 可以试试改在函数名里加上参数信息. 例如, 用 AppendString() 和 AppendInt() 等, 而不是一口气重载多个 Append(). 如果重载函数的目的是为了支持不同数量的同一 类型参数, 则优先考虑使用 std::vector 以便使用者可以用列表初始化 指定参数. 2.5.5 4.5. 缺省参数 总述 只允许在非虚函数中使用缺省参数, 且必须保证缺省参数的值始终一致. 缺省参数与函数重载 遵循同样 的规则. 一般情况下建议使用函数重载, 尤其是在缺省函数带来的可读性提升不能弥补下文中所提到的缺 点的情况下. 优点 有些函数一般情况下使用默认参数, 但有时需要又使用非默认的参数. 缺省参数为这样的情形提供了便 利, 使程序员不需要为了极少的例外情况编写大量的函数. 和函数重载相比, 缺省参数的语法更简洁明了, 减少了大量的样板代码, 也更好地区别了“必要参数”和“可选参数”. 缺点 缺省参数实际上是函数重载语义的另一种实现方式, 因此所有不应当使用函数重载的理由 也都适用于缺 省参数. 26 Chapter 2. C++ 风格指南 - 内容目录
Google开源项目风格指南虚函数调用的缺省参数取决于目标对象的静态类型,此时无法保证给定函数的所有重载声明的都是同样的缺省参数缺省参数是在每个调用点都要进行重新求值的,这会造成生成的代码迅速膨胀。作为读者,一般来说也更希望缺省的参数在声明时就已经被固定了,而不是在每次调用时都可能会有不同的取值缺省参数会干扰函数指针,导致函数签名与调用点的签名不一致而函数重载不会导致这样的问题结论对于虚函数,不允许使用缺省参数,因为在虚函数中缺省参数不一定能正常工作:如果在每个调用点缺省参数的值都有可能不同,在这种情况下缺省函数也不充许使用.(例如,不要写像voidf(intn=counter++);这样的代码.)在其他情况下,如果缺省参数对可读性的提升远远超过了以上提及的缺点的话,可以使用缺省参数,如果仍有疑惑,就使用函数重载2.5.64.6.函数返回类型后置语法总述只有在常规写法(返回类型前置)不便于书写或不便于阅读时使用返回类型后置语法定义C十+现在允许两种不同的函数声明方式以往的写法是将返回类型置于函数名之前.例如:int foo(int x);C++11引人了这一新的形式.现在可以在函数名前使用auto关键字,在参数列表之后后置返回类型。例如:auto foo(int x) -> int;后置返回类型为函数作用域,对于像int这样简单的类型,两种写法没有区别,但对于复杂的情况,例如类域中的类型声明或者以函数参数的形式书写的类型,写法的不同会造成区别.优点后置返回类型是显式地指定Lambda表达式的返回值的唯一方式.某些情况下,编译器可以自动推导出Lambda表达式的返回类型,但并不是在所有的情况下都能实现即使编译器能够自动推导,显式地指定返回类型也能让读者更明了有时在已经出现了的函数参数列表之后指定返回类型,能够让书写更简单,也更易读,尤其是在返回类型依赖于模板参数时.例如:template<class T,class U>auto add(Tt,Uu)->decltype(t+u);对比下面的例子:template<class T,class U> decltype(declval<T&>()+ declval<U&>()) add(Tt,Uu);272.5.4.函数
Google 开源项目风格指南 虚函数调用的缺省参数取决于目标对象的静态类型, 此时无法保证给定函数的所有重载声明的都是同样 的缺省参数. 缺省参数是在每个调用点都要进行重新求值的, 这会造成生成的代码迅速膨胀. 作为读者, 一般来说也更 希望缺省的参数在声明时就已经被固定了, 而不是在每次调用时都可能会有不同的取值. 缺省参数会干扰函数指针, 导致函数签名与调用点的签名不一致. 而函数重载不会导致这样的问题. 结论 对于虚函数, 不允许使用缺省参数, 因为在虚函数中缺省参数不一定能正常工作. 如果在每个调用点 缺省参数的值都有可能不同, 在这种情况下缺省函数也不允许使用. (例如, 不要写像 void f(int n = counter++); 这样的代码.) 在其他情况下, 如果缺省参数对可读性的提升远远超过了以上提及的缺点的话, 可以使用缺省参数. 如果 仍有疑惑, 就使用函数重载. 2.5.6 4.6. 函数返回类型后置语法 总述 只有在常规写法 (返回类型前置) 不便于书写或不便于阅读时使用返回类型后置语法. 定义 C++ 现在允许两种不同的函数声明方式. 以往的写法是将返回类型置于函数名之前. 例如: int foo(int x); C++11 引入了这一新的形式. 现在可以在函数名前使用 auto 关键字, 在参数列表之后后置返回类型. 例 如: auto foo(int x) -> int; 后置返回类型为函数作用域. 对于像 int 这样简单的类型, 两种写法没有区别. 但对于复杂的情况, 例如 类域中的类型声明或者以函数参数的形式书写的类型, 写法的不同会造成区别. 优点 后置返回类型是显式地指定Lambda 表达式 的返回值的唯一方式. 某些情况下, 编译器可以自动推导出 Lambda 表达式的返回类型, 但并不是在所有的情况下都能实现. 即使编译器能够自动推导, 显式地指定 返回类型也能让读者更明了. 有时在已经出现了的函数参数列表之后指定返回类型, 能够让书写更简单, 也更易读, 尤其是在返回类型 依赖于模板参数时. 例如: template <class T, class U> auto add(T t, U u) -> decltype(t + u); 对比下面的例子: template <class T, class U> decltype(declval<T&>() + declval<U&>()) add(T t, U u); 2.5. 4. 函数 27