与其它的面向对象编程语言类似,在 C++ 中,定义一个新的 class ,也就定义了一个新的type。一个 C++ 设计人员的大多数时间都会用在不断丰富充实他们的类型系统上(type system)。这意味着不仅仅是一个 class 的设计者,还是类型设计者。如重载函数和运算符、控制内存的分配和释放、定义对象的初始化和终止。因此应该和“语言设计者设计语言内置类型”一样的谨慎来对待类设计。 设计优秀的 class 工作艰难,因为设计好的types本身是一项艰巨的工作。好的类型应该有自然的语法、直观的语义,及一个或多个高效实现。在 C++ 中,一个不良规划下的class无法达到上述任一目标。甚至类的成员函数的声明方式也会影响到它的性能。 那么,如何设计高效classes?首先必须要了解所面对的问题。几乎所有的类都需要求考虑下面的问题,而你的答案往往导致你的设计规范: 1、新类型对象应如何创建和删除? 类中与之相关的函数包括:构造函数和析构函数,以及类中其它的内存分配和释放函数( operator new 、 operator new[] 、 operator delete 、 operator delete[] ,参见第 8 章)。如果你手动编写它们,这个问题的解决方式将会影响到这些函数。 2、对象初始化与对象赋值有什么不同? 这个问题的答案决定着构造函数与赋值运算符之间的区别。不要混淆初始化和赋值的概念,这一点很重要,因为它们对应于不同的函数调用。 3、对于新类型的对象如何通过传值方式传递? 拷贝构造函数定义了本类型如何通过传值来传递对象。 4、什么是新类型的“合法值”? 对class成员变量而言,只有某些数值集是合法的。这些集合类必须维护的约束条件。及成员函数必须要进行的错误检查工作,(尤其是构造函数、赋值运算符、以及“调节”函数)。它们会影响函数抛出的异常,以及(很少使用的)函数异常明细。 4新类型是否适用于继承? 如果新类是由现成的类继承而来的,那么就必须让新类符合继承的特征。尤其是要确定父类的成员函数是否应为虚函数(参见第 34 和第 36 条)。如果期望让其它的类可以继承你的class,就需要考虑本类的成员函数是否应为虚函数,尤其是它的析构函数(参见第 7 条)。 5、新类型允许进行哪些类型转换? 新类型存在于若干类型的大家族中,那么是否应该提供新类型与其它类型的转型功能?如果你期望类型T1隐私转换为 T2 。必须在T1 类中放置一个类型转换函数(比如 operator T2 ),或者在 T2 类中写一个non-explicit-one-argument(可被单一实参调用)的构造函数。如果只允许显式类型转换,就需要编写函数来执行这一转换,但是这一函数不应是类型转换运算符,也不应是单一参数的非 explicit 构 造函数(non-explicit-one-argument)。(第 15 条中有隐式 / 显式转换函数的示例。) 6、哪些运算符和函数对新类型是合理的? 这个问题取决于你会为你的类声明哪些函数。一些函应该是成员函数,而一些不是(参见第 23 、 24 、 46 条)。 7、应明确拒绝哪些标准函数? 通过将它们声明为 private 的可达到这一目的(参见第 6 条)。 8、谁可以访问新类型中的数据成员? 这一问题可以帮助我们确定哪些成员应声明为 public 的,哪些是 protected ,哪些是 private 。同时,也可以帮助我们确定哪些类和 / 或函数应该是友元,还有类的嵌套是否合理。
9、新类型中有哪些“尚未声明的接口”? 新类型中提供了哪些性能、异常安全(参见第 29 条)、资源使用保证(比如互斥锁、动态内存)?这些保证将会为类的实现提供更严格的约束。 8、新类型的通用性? 可能定义的并不仅是一个新类型。而是定义整个类型家族。如果这么做,需要定义一个新的类模板。 9、这个新类型是否满足了需求? 如果定义新类只是为已有类添加新功能。那么通过简单地定义一个或多个非成员函数或者模板可能会更好的达到目标。 这些问题不易回答,所以定义高效的类是一种挑战。然而,如果用户自定义的类可以像内建数据类型一样好用。一切都是值得的。 需要记住的: class 设计就是类型的设计。在定义新的类型之前,要确保上述问题考虑周全。