所有按引用传递的参数必须加上const.
定义:
在C语言中,如果函数需要修改变量的值,参数必须为指针,如intfoo(int*pval).在C++中,函数还可以声明引用参数:intfoo(intval).
优点:
定义引用参数防止出现(*pval)++这样丑陋的代码.像拷贝构造函数这样的应用也是必需的.而且更明确,不接受NULL指针.
缺点:
容易引起误解,因为引用在语法上是值变量却拥有指针的语义.
结论:
函数参数列表中,所有引用参数都必须是const:
voidFoo(conststringin,string*out);
事实上这在GoogleCode是一个硬性约定:输入参数是值参或const引用,输出参数为指针.输入参数可以是const指针,但决不能是非const的引用参数,除非用于交换,比如swap().
有时候,在输入形参中用constT*指针比constT更明智。比如:
您会传null指针。
函数要把指针或对地址的引用赋值给输入形参。
总之大多时候输入形参往往是constT.若用constT*说明输入另有处理。所以若您要用constT*,则应有理有据,否则会害得读者误解。
5.2.右值引用只在定义移动构造函数与移动赋值操作时使用右值引用.不要使用std::forward.
定义:
右值引用是一种只能绑定到临时对象的引用的一种,其语法与传统的引用语法相似.例如,voidf(strings);声明了一个其参数是一个字符串的右值引用的函数.
优点:
用于定义移动构造函数(使用类的右值引用进行构造的函数)使得移动一个值而非拷贝之成为可能.例如,如果v1是一个vectorstring,则autov2(std::move(v1))将很可能不再进行大量的数据复制而只是简单地进行指针操作,在某些情况下这将带来大幅度的性能提升.
右值引用使得编写通用的函数封装来转发其参数到另外一个函数成为可能,无论其参数是否是临时对象都能正常工作.
右值引用能实现可移动但不可拷贝的类型,这一特性对那些在拷贝方面没有实际需求,但有时又需要将它们作为函数参数传递或塞入容器的类型很有用.
要高效率地使用某些标准库类型,例如std::unique_ptr,std::move是必需的.
缺点:
右值引用是一个相对比较新的特性(由C++11引入),它尚未被广泛理解.类似引用崩溃,移动构造函数的自动推导这样的规则都是很复杂的.
结论:
只在定义移动构造函数与移动赋值操作时使用右值引用,不要使用std::forward功能函数.你可能会使用std::move来表示将值从一个对象移动而不是复制到另一个对象.
5.3.函数重载若要用好函数重载,最好能让读者一看调用点(callsite)就胸有成竹,不用花心思猜测调用的重载函数到底是哪一种。该规则适用于构造函数。
定义:
你可以编写一个参数类型为conststring的函数,然后用另一个参数类型为constchar*的函数重载它:
classMyClass{public:voidAnalyze(conststringtext);voidAnalyze(constchar*text,size_ttextlen);};
优点:
通过重载参数不同的同名函数,令代码更加直观.模板化代码需要重载,同时为使用者带来便利.
缺点:
如果函数单单靠不同的参数类型而重载(acgtyrant注:这意味着参数数量不变),读者就得十分熟悉C++五花八门的匹配规则,以了解匹配过程具体到底如何。另外,当派生类只重载了某个函数的部分变体,继承语义容易令人困惑。
结论:
如果您打算重载一个函数,可以试试改在函数名里加上参数信息。例如,用AppendString()和AppendInt()等,而不是一口气重载多个Append().
5.4.缺省参数我们不允许使用缺省函数参数,少数极端情况除外。尽可能改用函数重载。
优点:
当您有依赖缺省参数的函数时,您也许偶尔会修改修改这些缺省参数。通过缺省参数,不用再为个别情况而特意定义一大堆函数了。与函数重载相比,缺省参数语法更为清晰,代码少,也很好地区分了「必选参数」和「可选参数」。
缺点:
缺省参数会干扰函数指针,害得后者的函数签名(functionsignature)往往对不上所实际要调用的函数签名。即在一个现有函数添加缺省参数,就会改变它的类型,那么调用其地址的代码可能会出错,不过函数重载就没这问题了。此外,缺省参数会造成臃肿的代码,毕竟它们在每一个调用点(callsite)都有重复(acgtyrant注:我猜可能是因为调用函数的代码表面上看来省去了不少参数,但编译器在编译时还是会在每一个调用代码里统统补上所有默认实参信息,造成大量的重复)。函数重载正好相反,毕竟它们所谓的「缺省参数」只会出现在函数定义里。
结论:
由于缺点并不是很严重,有些人依旧偏爱缺省参数胜于函数重载。所以除了以下情况,我们要求必须显式提供所有参数(acgtyrant注:即不能再通过缺省参数来省略参数了)。
其一,位于.cc文件里的静态函数或匿名空间函数,毕竟都只能在局部文件里调用该函数了。
其二,可以在构造函数里用缺省参数,毕竟不可能取得它们的地址。
其三,可以用来模拟变长数组。
//通过空AlphaNum以支持四个形参stringStrCat(constAlphaNuma,constAlphaNumb=gEmptyAlphaNum,constAlphaNumc=gEmptyAlphaNum,constAlphaNumd=gEmptyAlphaNum);5.5.变长数组和alloca()
我们不允许使用变长数组和alloca().
优点:
变长数组具有浑然天成的语法.变长数组和alloca()也都很高效.
缺点:
变长数组和alloca()不是标准C++的组成部分.更重要的是,它们根据数据大小动态分配堆栈内存,会引起难以发现的内存越界bugs:“在我的机器上运行的好好的,发布后却莫名其妙的挂掉了”.
结论:
改用更安全的分配器(allocator),就像std::vector或std::unique_ptrT[].
5.6.友元我们允许合理的使用友元类及友元函数.
通常友元应该定义在同一文件内,避免代码读者跑到其它文件查找使用该私有成员的类.经常用到友元的一个地方是将FooBuilder声明为Foo的友元,以便FooBuilder正确构造Foo的内部状态,而无需将该状态暴露出来.某些情况下,将一个单元测试类声明成待测类的友元会很方便.
友元扩大了(但没有打破)类的封装边界.某些情况下,相对于将类成员声明为public,使用友元是更好的选择,尤其是如果你只允许另一个类访问该类的私有成员时.当然,大多数类都只应该通过其提供的公有成员进行互操作.
5.7.异常我们不使用C++异常.
优点:
异常允许应用高层决定如何处理在底层嵌套函数中「不可能发生」的失败(failures),不用管那些含糊且容易出错的错误代码(acgtyrant注:errorcode,我猜是C语言函数返回的非零int值)。
很多现代语言都用异常。引入异常使得C++与Python,Java以及其它类C++的语言更一脉相承。
有些第三方C++库依赖异常,禁用异常就不好用了。
异常是处理构造函数失败的唯一途径。虽然可以用工厂函数(acgtyrant注:factoryfunction,出自C++的一种设计模式,即「简单工厂模式」)或Init()方法代替异常,但是前者要求在堆栈分配内存,后者会导致刚创建的实例处于”无效“状态。
在测试框架里很好用。
缺点:
在现有函数中添加throw语句时,您必须检查所有调用点。要么让所有调用点统统具备最低限度的异常安全保证,要么眼睁睁地看异常一路欢快地往上跑,最终中断掉整个程序。举例,f()调用g(),g()又调用h(),且h抛出的异常被f捕获。当心g,否则会没妥善清理好。
还有更常见的,异常会彻底扰乱程序的执行流程并难以判断,函数也许会在您意料不到的地方返回。您或许会加一大堆何时何处处理异常的规定来降低风险,然而开发者的记忆负担更重了。
异常安全需要RAII和不同的编码实践.要轻松编写出正确的异常安全代码需要大量的支持机制.更进一步地说,为了避免读者理解整个调用表,异常安全必须隔绝从持续状态写到“提交”状态的逻辑.这一点有利有弊(因为你也许不得不为了隔离提交而混淆代码).如果允许使用异常,我们就不得不时刻