4. 函数
4.1. 参数顺序
总述
函数的参数顺序为: 输入参数在先, 后跟输出参数.
说明
C/C++ 中的函数参数或者是函数的输入, 或者是函数的输出, 或兼而有之. 输入参数通常是值参或const引用, 输出参数或输入/输出参数则一般为非const指针. 在排列参数顺序时, 将所有的输入参数置于输出参数之前. 特别要注意, 在加入新参数时不要因为它们是新参数就置于参数列表最后, 而是仍然要按照前述的规则, 即将新的输入参数也置于输出参数之前.
这并非一个硬性规定. 输入/输出参数 (通常是类或结构体) 让这个问题变得复杂. 并且, 有时候为了其他函数保持一致, 你可能不得不有所变通.
4.2. 编写简短函数
总述
我们倾向于编写简短, 凝练的函数.
说明
我们承认长函数有时是合理的, 因此并不硬性限制函数的长度. 如果函数超过 40 行, 可以思索一下能不能在不影响程序结构的前提下对其进行分割.
即使一个长函数现在工作的非常好, 一旦有人对其修改, 有可能出现新的问题, 甚至导致难以发现的 bug. 使函数尽量简短, 以便于他人阅读和修改代码.
在处理代码时, 你可能会发现复杂的长函数. 不要害怕修改现有代码: 如果证实这些代码使用 / 调试起来很困难, 或者你只需要使用其中的一小段代码, 考虑将其分割为更加简短并易于管理的若干函数.
4.3. 引用参数
总述
所有按引用传递的参数必须加上const.
定义
在 C 语言中, 如果函数需要修改变量的值, 参数必须为指针, 如intfoo(int*pval). 在 C++ 中, 函数还可以声明为引用参数:intfoo(int&val).
优点
定义引用参数可以防止出现(*pval)++这样丑陋的代码. 引用参数对于拷贝构造函数这样的应用也是必需的. 同时也更明确地不接受空指针.
缺点
容易引起误解, 因为引用在语法上是值变量却拥有指针的语义.
结论
函数参数列表中, 所有引用参数都必须是const:
void Foo(const string &in, string *out);
事实上这在 Google Code 是一个硬性约定: 输入参数是值参或const引用, 输出参数为指针. 输入参数可以是const指针, 但决不能是非const的引用参数, 除非特殊要求, 比如swap().
有时候, 在输入形参中用constT*指针比constT&更明智. 比如:
可能会传递空指针.
函数要把指针或对地址的引用赋值给输入形参.
总而言之, 大多时候输入形参往往是constT&. 若用constT*则说明输入另有处理. 所以若要使用constT*, 则应给出相应的理由, 否则会使得读者感到迷惑.
4.4. 函数重载
总述
若要使用函数重载, 则必须能让读者一看调用点就胸有成竹, 而不用花心思猜测调用的重载函数到底是哪一种. 这一规则也适用于构造函数.
定义
你可以编写一个参数类型为conststring&的函数, 然后用另一个参数类型为constchar*的函数对其进行重载:
class MyClass { public: void Analyze(const string &text); void Analyze(const char *text, size_t textlen);};
优点
通过重载参数不同的同名函数, 可以令代码更加直观. 模板化代码需要重载, 这同时也能为使用者带来便利.
缺点
如果函数单靠不同的参数类型而重载 (acgtyrant 注:这意味着参数数量不变), 读者就得十分熟悉 C++ 五花八门的匹配规则, 以了解匹配过程具体到底如何. 另外, 如果派生类只重载了某个函数的部分变体, 继承语义就容易令人困惑.
结论
如果打算重载一个函数, 可以试试改在函数名里加上参数信息. 例如, 用AppendString()和AppendInt()等, 而不是一口气重载多个Append(). 如果重载函数的目的是为了支持不同数量的同一类型参数, 则优先考虑使用std::vector以便使用者可以用列表初始化指定参数.
4.5. 缺省参数
总述
只允许在非虚函数中使用缺省参数, 且必须保证缺省参数的值始终一致. 缺省参数与函数重载遵循同样的规则. 一般情况下建议使用函数重载, 尤其是在缺省函数带来的可读性提升不能弥补下文中所提到的缺点的情况下.
优点
有些函数一般情况下使用默认参数, 但有时需要又使用非默认的参数. 缺省参数为这样的情形提供了便利, 使程序员不需要为了极少的例外情况编写大量的函数. 和函数重载相比, 缺省参数的语法更简洁明了, 减少了大量的样板代码, 也更好地区别了 “必要参数” 和 “可选参数”.
缺点
缺省参数实际上是函数重载语义的另一种实现方式, 因此所有不应当使用函数重载的理由也都适用于缺省参数.
虚函数调用的缺省参数取决于目标对象的静态类型, 此时无法保证给定函数的所有重载声明的都是同样的缺省参数.
缺省参数是在每个调用点都要进行重新求值的, 这会造成生成的代码迅速膨胀. 作为读者, 一般来说也更希望缺省的参数在声明时就已经被固定了, 而不是在每次调用时都可能会有不同的取值.
缺省参数会干扰函数指针, 导致函数签名与调用点的签名不一致. 而函数重载不会导致这样的问题.
结论
对于虚函数, 不允许使用缺省参数, 因为在虚函数中缺省参数不一定能正常工作. 如果在每个调用点缺省参数的值都有可能不同, 在这种情况下缺省函数也不允许使用. (例如, 不要写像voidf(intn=counter++);这样的代码.)
在其他情况下, 如果缺省参数对可读性的提升远远超过了以上提及的缺点的话, 可以使用缺省参数. 如果仍有疑惑, 就使用函数重载.
4.6. 函数返回类型后置语法
总述
只有在常规写法 (返回类型前置) 不便于书写或不便于阅读时使用返回类型后置语法.
定义
C++ 现在允许两种不同的函数声明方式. 以往的写法是将返回类型置于函数名之前. 例如:
int foo(int x);
C++11 引入了这一新的形式. 现在可以在函数名前使用auto关键字, 在参数列表之后后置返回类型. 例如:
auto foo(int x) -> int;
后置返回类型为函数作用域. 对于像int这样简单的类型, 两种写法没有区别. 但对于复杂的情况, 例如类域中的类型声明或者以函数参数的形式书写的类型, 写法的不同会造成区别.
优点
后置返回类型是显式地指定Lambda 表达式的返回值的唯一方式. 某些情况下, 编译器可以自动推导出 Lambda 表达式的返回类型, 但并不是在所有的情况下都能实现. 即使编译器能够自动推导, 显式地指定返回类型也能让读者更明了.
有时在已经出现了的函数参数列表之后指定返回类型, 能够让书写更简单, 也更易读, 尤其是在返回类型依赖于模板参数时. 例如:
template
对比下面的例子:
template
缺点
后置返回类型相对来说是非常新的语法, 而且在 C 和 Java 中都没有相似的写法, 因此可能对读者来说比较陌生.
在已有的代码中有大量的函数声明, 你不可能把它们都用新的语法重写一遍. 因此实际的做法只能是使用旧的语法或者新旧混用. 在这种情况下, 只使用一种版本是相对来说更规整的形式.
结论
在大部分情况下, 应当继续使用以往的函数声明写法, 即将返回类型置于函数名前. 只有在必需的时候 (如 Lambda 表达式) 或者使用后置语法能够简化书写并且提高易读性的时候才使用新的返回类型后置语法. 但是后一种情况一般来说是很少见的, 大部分时候都出现在相当复杂的模板代码中, 而多数情况下不鼓励写这样复杂的模板代码.
5. 来自 Google 的奇技
Google 用了很多自己实现的技巧 / 工具使 C++ 代码更加健壮, 我们使用 C++ 的方式可能和你在其它地方见到的有所不同.
5.1. 所有权与智能指针
> 总述
动态分配出的对象最好有单一且固定的所有主, 并通过智能指针传递所有权.
> 定义
所有权是一种登记/管理动态内存和其它资源的技术. 动态分配对象的所有主是一个对象或函数, 后者负责确保当前者无用时就自动销毁前者. 所有权有时可以共享, 此时就由最后一个所有主来负责销毁它. 甚至也可以不用共享, 在代码中直接把所有权传递给其它对象.
智能指针是一个通过重载*和->运算符以表现得如指针一样的类. 智能指针类型被用来自动化所有权的登记工作, 来确保执行销毁义务到位.std::unique_ptr是 C++11 新推出的一种智能指针类型, 用来表示动态分配出的对象的独一无二的所有权; 当std::unique_ptr离开作用域时, 对象就会被销毁.std::unique_ptr不能被复制, 但可以把它移动(move)给新所有主.std::shared_ptr同样表示动态分配对象的所有权, 但可以被共享, 也可以被复制; 对象的所有权由所有复制者共同拥有, 最后一个复制者被销毁时, 对象也会随着被销毁.
> 优点
如果没有清晰、逻辑条理的所有权安排, 不可能管理好动态分配的内存.
传递对象的所有权, 开销比复制来得小, 如果可以复制的话.
传递所有权也比”借用”指针或引用来得简单, 毕竟它大大省去了两个用户一起协调对象生命周期的工作.
如果所有权逻辑条理, 有文档且不紊乱的话, 可读性会有很大提升.
可以不用手动完成所有权的登记工作, 大大简化了代码, 也免去了一大波错误之恼.
对于 const 对象来说, 智能指针简单易用, 也比深度复制高效.
> 缺点
不得不用指针(不管是智能的还是原生的)来表示和传递所有权. 指针语义可要比值语义复杂得许多了, 特别是在 API 里:这时不光要操心所有权, 还要顾及别名, 生命周期, 可变性以及其它大大小小的问题.
其实值语义的开销经常被高估, 所以所有权传递带来的性能提升不一定能弥补可读性和复杂度的损失.
如果 API 依赖所有权的传递, 就会害得客户端不得不用单一的内存管理模型.
如果使用智能指针, 那么资源释放发生的位置就会变得不那么明显.
std::unique_ptr的所有权传递原理是 C++11 的 move 语法, 后者毕竟是刚刚推出的, 容易迷惑程序员.
如果原本的所有权设计已经够完善了, 那么若要引入所有权共享机制, 可能不得不重构整个系统.
所有权共享机制的登记工作在运行时进行, 开销可能相当大.
某些极端情况下 (例如循环引用), 所有权被共享的对象永远不会被销毁.
智能指针并不能够完全代替原生指针.
> 结论
如果必须使用动态分配, 那么更倾向于将所有权保持在分配者手中. 如果其他地方要使用这个对象, 最好传递它的拷贝, 或者传递一个不用改变所有权的指针或引用. 倾向于使用std::unique_ptr来明确所有权传递, 例如:
std::unique_ptr
如果没有很好的理由, 则不要使用共享所有权. 这里的理由可以是为了避免开销昂贵的拷贝操作, 但是只有当性能提升非常明显, 并且操作的对象是不可变的(比如说std::shared_ptr
不要使用std::auto_ptr, 使用std::unique_ptr代替它.
5.2. Cpplint
> 总述
使用cpplint.py检查风格错误.
> 说明
cpplint.py是一个用来分析源文件, 能检查出多种风格错误的工具. 它不并完美, 甚至还会漏报和误报, 但它仍然是一个非常有用的工具. 在行尾加//NOLINT, 或在上一行加//NOLINTNEXTLINE, 可以忽略报错.
某些项目会指导你如何使用他们的项目工具运行cpplint.py. 如果你参与的项目没有提供, 你可以单独下载cpplint.py.
译者(acgtyrant)笔记
把智能指针当成对象来看待的话, 就很好领会它与所指对象之间的关系了.
原来 Rust 的 Ownership 思想是受到了 C++ 智能指针的很大启发啊.
scoped_ptr和auto_ptr已过时. 现在是shared_ptr和uniqued_ptr的天下了.
按本文来说, 似乎除了智能指针, 还有其它所有权机制, 值得留意.
Arch Linux 用户注意了, AUR 有对 cpplint 打包.
-
Google
+关注
关注
5文章
1752浏览量
57349 -
编程
+关注
关注
88文章
3556浏览量
93519 -
C++
+关注
关注
21文章
2094浏览量
73442
原文标题:Google C++ 编程规范 - 3
文章出处:【微信号:C_Expert,微信公众号:C语言专家集中营】欢迎添加关注!文章转载请注明出处。
发布评论请先 登录
相关推荐
评论