C++编程准则(2)

来源:互联网 发布:手机画画软件sketchbook 编辑:程序博客网 时间:2024/05/16 15:57

16.        注意长的参数表,这样的函数调用会难写、难读、难于维护。应该把这个函数改成一个合适的类,用对象作为参数传递。

17.        不要自我重复。如果一段代码在派生类的许多函数中重复出现,就把这段代码放在基类的一个单一的函数中然后在派生类中调用它。这样既节省了代码空间,又使将来的修改容易传播。我们可以用内联函数来提高效率。有时我们会发现这种通用代码会为我们的接口添加有用的功能。

18.        注意switch和if-else语句。他们是典型的类型检查编码的指示符。意味着程序运行的情况和我们的类型信息有关。(实际的类型也许不是我们最初看起来的类型)我们通常可以将这些代码转换成继承或多态,多态会为我们进行类型检查,使程序可靠和已与扩展。

19. 从设计的角度,寻找并区分那些变化和不变的成分。也就是在系统中寻找那些修改时不需要重新设计的成分,把他们封装到一个类中。

20. 注意不同点。两个语义上不同的对象可能有同样的操作或反应,自然就会试着把一个作为另一个的子类以便利用继承性的好处。这就叫差异,但并没有充分的理由来强制这种并不存在的父子关系。一个好的解决办法是产生一个共同的父类:它包含两个子类——这可能要多占一点空间,但我们可以从继承中获益,并且可能对这种设计有重要发现。

21. 注意在继承过程中的限制。最清晰地设计是向被继承者加入新的功能,而如果在继承过程删除了原有功能,而不是加入新功能,那这个设计就值得怀疑了。但这也不是绝对的,如果我们正在与一个老的类库打交道,对已有的类在子类中进行限制可能更有效,而不必重建一套类层次来使我们的新类适应新的应用。

22. 不要用子类去扩展基类的功能。如果一个类接口部分很关键的话,应当把它放在基类中,而不是在继承中加入。如果我们正在用继承来添加成员函数,我们可能应该重新考虑我们的设计。

23. 一个类一开始时接口部分应尽可能小而精。在类使用过程中,我们会发现需要扩展类的接口。然而一个类一旦投入使用,我们要想减少接口部分,就会影响那些使用了该类的代码,但如果我们我们需要增加函数则不会有影响,一切正常,只需要重新编译一下即可。但即使用新的成员函数取代了原来的功能,也不要去改正原有接口(如果我们愿意的话,可以再低层将两个函数合并。)如果我们需要对一个已有的函数增加参数,我们可以让原来的参数保持不变,把所有新参数作为默认参数,这样不会妨碍对该函数已有的调用。

24. 大声朗读我们的类,确保他们是合理的。读基类时用“is-a”,读成员对象时用“has-a”。

25. 在决定是用继承还是用组合时,问问自己是不是需要向上类型转换到基类。如果不需要,就用组合(成员对象)而不用继承。这样可以减少多重继承的可能。如果我们选择继承,用户会认为他们被假设向上类型转换。

26. 有时我们为了访问基类中的protected成员而采用继承。这可能导致一个可察觉的对多重继承的需求。如果我们不需要向上类型转换,首先导出一个新类来完成保护成员的访问,然后把这个新类作为一个成员对象,放在需要用到它的所有对象中去。

27. 一个典型的基类仅仅是它的派生类的一个接口。当我们创建一个基类时,默认情况下让成员函数都成为纯虚函数。析构函数也可以是纯虚函数(强制派生类对它重新定义),但记住要给析构函数一个函数体,因为继承关系中所有的析构函数总是被调用。

28. 当我们在类中放一个虚函数时,让这个类的所有函数都成为虚函数,并在类中定义一个虚析构函数。只有当我们要求高效时,而且分析工具指出应该这样做,再把virtual关键字去掉。

29. 用数据成员表示值的变化,用虚函数表示行为的变化。如果我们发现一个类中有几个状态变量和几个成员函数,而成员函数在这些变量的作用下改变行为,我们可能要重新设计它,用子类和虚函数来区分这种不同的作用。

30. 如果我们必须做一些不可移植的事,对这种服务做一个抽象并将它定位在一个类的内部,这个额外的间接层可防止这种不可移植性影响我们的整个程序。

31. 尽量不用多重继承。这可帮助我们摆脱困境,尤其是修复我们无法控制的类的借口时。除非我们是一个经验相当丰富的程序员,否则不要在系统中设计多重继承。

32. 不要用私有继承。虽然C++中可以有私有继承,而且似乎在某些场合下很有用,但它和运行时类型识别一起使用时,常常引起语义的模棱两可。我们可以用一个私有成员对象来代替私有继承。

33. 如果两个类因为一些函数的关系(如容器和迭代器)而联系在一起,使一个类设为公有并将另一个类包含成友元。这不仅强调二者之间的联系,而且允许一个类的名字嵌入到另一个类中复用。标准C++通过在每个容器类中定义嵌入的迭代器类,为容器提供了通用接口。嵌入的另一个原因是可以作为私有运行的一部分。这里,嵌入比类之间的联系提供了更大的运行隐藏,而且防止出现上面提到的名字空间污染。

34. 运算符重载仅仅是“语法糖”:另一种函数调用方法。如果重载一个运算符不会使类的接口更清楚、更易于使用,就不要重载它。一个类只创建一个自动类型转换运算符。

35. 首先保证程序能运行,然后再考虑优化。特别是,不要急于写内联函数、使一些函数为非虚函数或者紧缩代码以提高效率。这些在我们开始构建系统时都不用考虑。我们开始的目标应该是证明设计的正确性,除非设计要求一定的效率。

36. 不要让编译器来为我们产生构造函数、析构函数或operator=。类的设计者应该明确地说出类应该做什么,并完全控制这个类。如果我们不想要拷贝构造函数或operator=,就把他们声明为私有的。记住,只要我们产生了任何构造函数,就防止了默认构造函数被生成。

37. 如果我们的类中包含指针,我们必须产生拷贝构造函数、operator=和析构函数,以使类运行正常。

38. 当为派生类写拷贝构造函数时,记住要显示调用基类的拷贝构造函数。如果不这样做,基类会调用默认构造函数,可能这不是我们所想要的情形。要调用基类拷贝构造函数,用以下方式将它传给派生类:Derived(const Derived& d):Base(d){//...

39. 当为派生类写赋值操作符时,记住显示调用基类版本。如果不如此,就不会起作用。应用基类的名字和作用域操作符,调用基类的赋值运算符:

      Derived&operator=(const Derived& d){ Base::operator=(d);

40. 为了减少大项目开发过程中的重复编译,应使用句柄类/Cheshire cat技术,只有需要提高运行效率时才把它去掉。
原创粉丝点击