1.类设计的建议
C++和其它面向对象的语言一样,,定义一个类就定义了一个新的类型。重载函数和操作符,内存的分配与释放,对象的构造与析构,全部掌握在你的手上。所以在设计类的时候,要像语言的设计者设计内置类型一样小心。
好的类型拥有自然的语法,直观的语义和高效率的实现。如何高效地设计一个类呢? 以下的问题在几乎所有的类型设计中都会遇到,以及考虑这些问题会如何影响到你的设计:
(1) 新类型的对象要如何创建和销毁?
这决定了要如何写构造函数和析构函数,包括要使用什么内存分配和释放函数,即new还是new[],delete还是delete[]。请参见前面的文章C++要成对使用new和delete且采取相同形式。
(2) 对象的初始化和对象的赋值应该有何差别?
这决定了你如何写,如何区别构造函数和赋值运算符,以及不要把初始化与赋值混淆,因为它们的语义不同,构造函数适用于未创建的对象,赋值适用于已创建的对象,这也是为什么我们要在构造函数中使用初始化列表而不使用赋值的原因。请参见前面的文章确定对象使用前已先被初始化和C++中要完整拷贝对象。
(3) 新类型的对象如果以传值方式传递,意味着什么?
要记住拷贝构造函数决定了你的类型是如何被传值的,因为传值会生成本地的拷贝。
(4) 什么是新类型的合法值?
通常情况下,并不是成员的任何数值组合都是合法的。要让数据成员合法,我们需要根据合法的组合,在成员函数中对数值进行检测,尤其是构造函数,赋值运算符和setter。这也会影响到使用它的函数会抛出什么异常。
(5) 新类型属于某个继承层级吗?
如果你的新类型继承自某个已有的类,你的设计将被这些父类影响到,尤其是父类的某些函数是不是虚函数。如果你的新类型要作为一个父类,你将要决定把哪些函数声明为虚函数,尤其要注意析构函数。具体请参见前面的文章C++中为多态基类声明虚析构函数。
(6) 新类型允许进行什么样的转换?
新类型的对象将会在程序的海洋中与其它各种各样的类型并用,这时你就要决定是否允许类型的转换。如果你希望把T1隐式转换为T2,你可以在T1中定义一个转换函数,例如operator T2,或者在T2中定义一个兼容T1的不加explicit修饰的构造函数。如果希望使用显式转换,你要定义执行显示转换的函数。请参见前面的文章C++在资源管理类中提供对原始资源的访问。
(7) 什么样的运算符和函数对新类型是有意义的?
这决定了你要声明哪些函数,包括成员函数,非成员函数,友元函数等。
(8) 禁止哪些标准函数?
如果不希望使用编译器会自动生成的标准函数,把它们声明为私有。请参见前面的文章如果不想使用编译器默认生成的函数,请明确拒绝它!。
(9) 谁该取用新类型的成员?
这影响到哪些成员是公有的(public),哪些是保护的
(protected),哪些是私有的(private)。这也能帮你决定哪些类和函数是友元的,以及要不要使用嵌套类(nested class)。
(10) 新类型的隐藏接口是什么?
新类型对于性能,异常安全性,资源管理(例如锁和内存)有什么保障? 哪些问题是自动解决不需要用户操心的? 要实现这些保障,自然会对这个类的实现产生限制,例如要使用智能指针而不要使用裸指针。
(11) 新类型的通用性
如果想让你的新类型通用于许多类型,定义一个类模板(class template),而不是单个新类型。
(12) 新类型真的是你所需要的吗?
如果你想定义一个子类只是为了给基类增加某些新功能,定义一些非成员的函数或者函数模板更加划算。
2.总结
(1) 类的设计犹如类型的设计。在定义一个新类型之前,请确定你已经考虑过上面所讨论的全部建议。