条款19:设计 class 犹如设计 type
在看到”class”和”type”,就一定要先区别一下这两者:
class - 其意思是自定义的类,需要自己控制一切;
type - 其意思是系统预定义的类,类似int、double、string、vector,别人写好给大家用的。
因此,此条款就是在强调,自定义的类要参考系统预设的类,看看别人的类是怎么写的,它是为什么好用,为什么安全的,这对设计思想提出了较高的要求。
原书中也给出了一些基础性的参考问题,如下:
一、需要回答的问题
1、对象如何去创建和销毁?
这里个人觉得是要考虑构造函数和析构函数内要做什么,比如考虑性能就要记得处理指针成员变量,是否要在构造就去new,析构要不要处理delete等。
详见 第八章 的设计思路。
2、对象初始化与对象的赋值应该有什么样的差别?
切莫混淆 “初始化” 和 “赋值”。
详见 第四章 的设计思路
3、新type如果被pass-by-value意味着什么?
此问题关注点在于拷贝构造函数,要注意区分 浅拷贝 和 深拷贝 ,进而根据情况进行选择。
4、什么是新type的合法值?
合法值,最初只是以为是要考虑数值的约束范围,就像 第十八章 中所讲,后来发现还需要考虑约束范围不清楚的情况下,抛出异常了要怎么处理,虽然并不常见。
5、你的新type需要配合某个继承图系吗?
考虑配合继承图系的时候,要注意两种情况:
a、要继承已有类,就天然受到被继承类的限制约束,比如那些需要重写,那些需要重载等等;
b、可能被后续类继承,就要本类的拓展,哪些是图系不需要更改的重要特征,哪些是后续可以自定义的部分,甚至是必须自定义;
这部分要深挖,其实可以结合设计模式。
6、新type需要什么样的转换?
主要是针对隐式转换,operator OtherType() const,但通常情况下隐式转换也意味着隐患,所以设计时要谨慎。另外,构造函数中也要当心,如果不想让隐式构造发生,就要在前面加上explicit关键字。举个例子:
class A
{
private:
int a;
public:
A(int b):a(b){}
};
void fun(A obj);
若调用fun(3),则编译器也能接受,因为编译器自动作了fun(A(3))的处理,这就是隐式构造。而如果用户自己写fun(A(3)),这是显式构造。当A的构造函数前有explicit时,fun(3)的调用将通不过编译器。通常情况下,隐式转换是安全的。
7、什么样的操作符和函数对此新type而言是合理的?
就是设计什么样的成员函数,以及重载哪些运算符。
8、 什么样的标准函数应该驳回?
这一条就是在针对 private , public 和 protected三兄弟,前两者在普通类中相当常见,就是用来处理那些可以直接而自由的访问,那些必须用间接而带有限制的访问,这些都是基本功;
而protected是有继承关系的类中使用的,也可以说是一个进阶的方向。
详见 第六章 的设计思路。
9、 谁该取用新type成员?
与上面一条类似,但这一条正偏向于自定义类中的成员函数,比如有些成员函数不可以自由修改,就要用private ,然后用get, set方法来限制性的使用;
10、 什么是新type的未声明接口?
个人对这条的理解暂时是,因为c++是.h文件声明,.cpp文件实现的,因此,有些函数是可以不放在.h文件中声明,进而防止被包含这个.h文件的类使用的,要学会分析情况,灵活使用这种方法,也要注意出现的异常等问题。
11、 你的新type有多么一般化?
这牵涉到泛型编程了,也就是模板,可以参考STL各种容器的实现,体会泛型的强大。
12、 你真的需要一个新type吗?
请把这一条放在最前面,先去考虑自使用定义类达成的目标,是否可以用更简单的结构来达成,比如一个模板。
二、总结
Class 的设计就是 type 的设计。在定义一个新的 type 之前,请确定你已经考虑过本条款覆盖的所有讨论主题。