文章目录
设计一个函数
函数接口规格说明
在确定C++函数接口时,有下列问题需要研究:
- 运算符或者非运算符函数
- 自由或者成员运算符
- 虚函数或者非虚函数
- 纯虚成员函数或非纯虚成员函数
- 静态或非静态成员函数
- const或非const成员函数
- 公共的、保护的还是私有的成员函数
- 通过值、引用还是指针返回
- 返回const还是非const
- 参数是可选的还是必需的
- 通过值、引用还是指针传递参数
- 将参数作为const还是非const传递
有两个组织问题,虽然不是逻辑接口的一部分但是也必须考虑:
- 友元或者非友元函数
- 内联或者非内联函数
运算符或者非运算符
可读性应该是使用运算符重载的主要原因
一个重载运算符的语义对客户应该是自然的、明显的和直观的
用户自定义类型的重载运算符的语法属性,应该反应已经为基本类型定义了的属性
让用户自定义运算符的语法术语模仿预先确定的C++运算符,以避免意外并使他们的使用更可以预知
自由或成员运算符
C++语言本身可作为用户自定义运算符模仿的一个客观和适宜的标准
重载运算符中的不一致问题,对客户来说可能是明显的,讨厌的和高代价的
虚函数或非虚函数
不必为了获得多态行为而损害语法问题,例如双目运算符的对称隐式转换
虚函数实现行为上的变化;数据成员实现值的变化
隐藏:一个成员函数若在与一个基类或文件作用域中声明的某个函数同名,则隐藏基类中的函数
重载:一个函数用定义在同一个作用域的同样的名称重载了另一个函数的名称覆盖:一个成员函数覆盖了在一个基类中声明的虚拟的同样的函数
重新定义:一个函数的默认定义被另一个定义不可挽回的取代
避免将一个基类函数隐藏在一个派生类中
纯虚函数或者非纯虚函数
静态或者非静态成员函数
静态成员函数通常用于实现一个单独的工具类中的非基本功能
const 成员函数或者非const成员函数
如果一个函数只有一个单一参数,该参数是指向某个对象的引用,并且若不经过显式的转换就不能从函数体 内部获得一个指向同一个对象的非const引用,那么这个对象是const正确的
系统中的每一个对象都应该是const正确的
从一个const成员函数返回一个非const对象可能会破坏一个系统的const正确性
在一个系统中,如果一个只有一个参数的函数未通过显式转换就不能获得一个指向这些对象中的任何一个的可写引用,那么这个系统就是const正确的
一个系统应该是const正确的
如果一个系统的转换图未包含同时涉及任何一个类型的const和非const版本的循环,那么这个系统就是const正确的
在抛弃const的时候至少考虑2次
公共的、保护的或者私有的成员函数
如果成员函数不是公共的,那么会使普通用户也要接触为绝缘的实现细节
所有的虚函数和保护函数都是派生类作者考虑的事项
通过值、引用或者指针返回
对于返回一个错误状态来说,整数值0应该总是意味着成功
通常函数成功工作只有一种方式,而它失败却有若干种方式;作为客户,我们可以不关心它为什么失败
通过加载一个可修改的句柄参数返回一个动态分配对象,比通过非const指针返回那个对象更不容易产生内存泄漏
回答是或者否的函数应该被适当命名(如isValid),并且返回一个不是0就是1的值
返回const 或者非const
避免将结果(从函数通过值返回的)声明为const
可选参数或者必要参数
默认参数可以是函数重载的一种有效替代品,尤其是在不涉及绝缘的地方
避免那些需要一个未命名的临时对象的结构的默认参数
通过值、引用还是指针来传递参数
千万不要通过值传递一个用户自定义类型个一个函数
通过参数返回值要保持一致(例如,避免声明非const引用参数)
避免将一个函数的任何参数的地址存储在函数结束后仍会保留的位置;改为传递该参数的地址。
永远不要企图删除一个通过引用传递的对象
将参数作为const还是非const传递
如果无论何时一个形参通过引用或者指针传递其实参给一个函数,该函数都既不修改这个实参也不存储它的可写地址,那么这个形参就应该声明为const
避免将通过值传递给一个函数的形参声明为const
考虑将那些会激活可修改访问的形参(也许除了那些有默认实参的形参)放在那些通过值、const引用或const指针来传递实参的形参之前。
友元或非友元函数
避免不必要的友元关系可以提高可维护性
避免给单独的函数授予友元关系
内联或非内联函数
如果函数体内产生的对象的代码大于同样的非内联函数调用本身所产生的对象代码,那么应该避免将该函数声明为inline
若你的编译器不会产生内联函数,那么应该避免将一个函数声明为inline
在接口中使用基本类型
在接口中使用short
避免在接口中使用short,改为使用int。
在代码中直接明确地说明设计决策(而不是依赖于注释)是一个设计目标;设计能安全使用和容易维护的健壮性接口偶尔会与这个目标形成竞争关系
在接口中使用unsigned
避免在接口中使用unsigned整数,改为使用int
有时候注释比直接在代码中表达一个接口决策要更好
在接口中使用long
在接口中避免使用long ;改为assert(sizeof(int)>=4),并使用int或者一个用户自定义的大整数类型
在接口中使用float、double和long double
考虑在接口中对于浮点类型只使用double,除非有强制性的原因才使用float或者long double
在实际出现的大多数情况下,为了在接口中能表达整数和浮点数,所需要的唯一基本类型就是int和double
特殊情况函数
转换运算符
隐式转换会影响类型安全,会引入二义性,并且一般来说会增加维护程序的开销
会激活隐式转换的构造函数,尤其是从广泛使用的类型或者基本类型的转换,会破坏由强类型所提供的安全性
考虑避免cast运算符,尤其是对基本整数类型,改为进行显式的转换
编译器产生的值语义
为任何定义在头文件中的类显式的声明构造函数和赋值运算符,甚至在默认的实现是充分的情况下
析构函数
在每一个声明了一个虚函数的类中,把析构函数显式地声明为类中的第一个虚函数,并且非内联的定义
在没有另外声明虚函数的类中,显式地把析构函数声明为非虚拟的并且适当地对它进行定义(内联的或者非内联的)
小结
C++语言在描述函数级接口方面提供了大量的灵活性。当我们设计每个函数时,我们必须至少研究14个独立的维妮塔。每个都决定都会引起必须在上下文中解决的额外考虑:
- 运算符或者非运算符函数
- 自由运算符或者成员运算符
- 虚函数或者非虚函数
- 纯虚成员函数或者非纯虚成员函数
- 静态或者非静态成员函数
- const或者非const成员函数
- 公共的保护的或者私有的成员函数
- 通过值、引用或者指针返回
- 返回const或者非const
- 实参是可选的还是必须的
- 通过值、引用或者指针传递实参
- 将实参作为const或者非const传递
- 友元或者非友元函数
- 内联或者非内联函数
在函数的接口中有许多可选择的整数类型可供使用:short、unsigned、long等。在实际中一个32位机器上,在接口中我们需要的唯一整数类型是int,任何使用其他类型都潜在的存在效率低,不能封装、易于出错或者只是使用起来很麻烦的可能性
在C++中有三种可能选择浮点类型:float、double以及long double。在C中,在将浮点数作为参数传递之前,传统上将所有的浮点参数都转换成double。大多数硬件都适用于尽可能有效地处理double值。所有的浮点数都应该在接口中被表达为double,除非有强制原因要求不这么做
转换运算符与编译时类型安全是相互竞争的关系。接受单个参数的构造函数会激活隐式转换。但是这样的结构是许多接口的必要的一个部分。另一方面,转换运算符是容易避免的,只要提供显式的转换函数即可。有时候通过隐式转换可以增强可用性。但是在大多情况下,隐式转换是一种倾向,尤其是当他包含一个基本整数类型时。
C++编译器会自动产生一些未定义的函数。不依赖默认行为有很多原因,尤其是当接口在整个系统中被广泛使用时。许多C++实现依赖至少有一个非内联函数定义的虚函数。在我们的风格中,这个虚函数总是析构函数。一些当前流行的编译器不允许显式地调用没有显式声明的析构函数。在实践中,显式地定义每个函数的析构函数都是明智的。对于没有虚函数的类来说,内联地定义还是非内联的定义析构函数都是合适的。对于有虚函数的类来说,应该非内联地定义析构函数。对于协议类,析构函数应该为空。