析构函数
如果一个类想被别人继承,通常析构函数要声明为虚函数,否则,如下代码就会出现未定义情况。
Base* p=new Derived();
delete p;
这句话反过来说,就是如果你不打算让别人派生你的类,就不要把析构函数声明为虚函数,当然对方是菜鸟的话,你也没办法。
极端特殊情况下,只要使用者保证不会多态的析构(像上面的例子),也可以通融。
继承与访问权限
基类的private成员,只能由基类或者基类的友元访问,也就是说既不能被类外部的调用者(客户)访问,也不能被子类访问。
或许是提供一种相对宽松的选择,protected成员允许派生类访问,但是拒绝外部调用者的访问。<<C++Primer>>提供了一个细节的区别,派生类不能通过“基类对象.保护成员变量”这种方式使用protected成员变量,但是可以通过”派生类对象.保护成员变量”这种方式使用。当然直接使用自己继承而来的protected成员变量是可行的,因为实际上等价于this->保护成员变量。
实际上,子类只能访问基类的public和protected成员。如果是公有继承,基类的public和protected成员在子类中仍然是public和protected的;如果是受保护继承,则都变成了protected成员;如果是私有继承,则都变成了private成员。
公有继承
绝大多数情况下,公有继承应该符合下列两个条件:
1) Derived class
IS-A base class (IS-A 原则)
2) Derived class
WORKS-LIKE-A base class(WORKS-LIKE-A原则)
一个符合第一个原则,但不符合第二个原则的例子----基类Rectangle和公有派生类Square。Rectangle提供了虚函数SetWidth用来允许客户设置宽度,但是派生类将重载该方法,并且保证宽度修改的同时高度也已经被修改。Square类的行为不再表现的像它的父类一样,所以不能使用公有继承表达他们之间的关系,尽管数学上正方形是矩形的一种,可这是c++,不是数学领域。:)
这两个原则反映了面向对象的五大原则之一:LSP(Liskov Substitution Principle).
有这么一种极端情况,如果设计者能够保证用户没有机会以多态的方式来使用子类,出于方便的目的使用公有继承也是可以的。比如<<Exceptional C++>>关于ci_char_traits公有继承char_traits<char>类,并改写其中的几个静态成员函数的例子。因为STL内部不会以多态方式使用该类。
这里使用的是新的替换原则:GLSP,G代表泛型。作为模板参数传递的任何类型都需要遵守这个参数的需求,提供一些成员函数。
保护继承
保护继承通常用于再继承的情况,如果我设计的类需要继承某个类的公有和保护成员,并且我还希望这些成员能够被我设计的类的派生类访问,这时候我可以使用保护继承。当然,公有继承也能够达到这个目的,但是公有继承需要遵守两个原则,在此之外,我们需要保护继承。
保护继承只是继承某个类的现有的实现,不需要遵守公有继承的两个原则,或者说,Derived class is not a base class,and will not work like a base class。
私有继承
私有继承的目的和保护继承差不多,只是所有的基类成员都会被变成private成员,因此不可被继承。
私有继承和保护继承都表达同一种意思,我利用现有的某个类的实现来完成我的类的实现,只是为了代码重用,别无它的目的。(IS-IMPLEMENTED-IN-TERMS-OF 根据某物实现出)。
继承与耦合度
无论如何继承总是将派生类和父类高强度的联系到一起,所以<<Exceptional C++>>总是建议尽可能的HAS-A的使用优先于继承。
这样编码工作可能较大,但是带来了更多的灵活性,比如可以包含多个实例,可以将数据成员隐藏到编译器防火墙后面(利用Pimp技术)。
多重继承
如果多个父类拥有同样的名字成员,那么子类调用的时候,编译器就会报模棱两可的错误。显示的指出你打算使用哪个父类的成员是一个解决办法。
如果你的两个父类又继承自同一个祖父类,这就是钻石形继承。所有祖父类的成员都会被你的两个父类继承两次,其结果是:1)祖父类的成员变量将会在他的两个子类中各有一个副本,其结果是你的类最后拥有两个副本;2)使用继承而来的祖父类的成员会导致编译器的模棱两可,因为有两条路径阿。
Adapter模式有一种实现方法就是使用多重继承,Adapter公有继承自Target,同时私有继承自Adaptee。不过按照<<Exceptional C++>>的原则,尽可能的使用Has-A的方法代替私有继承,除非要访问Adaptee的protected成员。
虚继承
虚继承能够解决钻石形多重继承导致的多个祖父类成员变量副本的问题。<<Effective C++>>提醒,虚继承会带来额外的空间和时间的开销。所以并不是使用虚继承就一定好。
虚基类最好不要放成员变量,这是因为总是由最底层的派生类负责初始化虚基类。这点<<Effective C++>>并没有交待清楚,不知道初始化的内幕是什么?
重载函数
如果不是虚函数,请不要重载。原因有二:
1)因为它不具备虚函数的动态绑定特征,所以当你使用指向基类的指针去调用某个函数,该函数已经被你重载,并且指针实际上指向的的确是你的子类,编译器却绝对不会调用子类的版本,因为这个时候指针的静态类型说了算,调用基类的版本。
2)非虚函数本身就意味着派生类需要继承接口和实现,不应该修改,因为这代表着不变性凌驾于特异性。
参考:<<C++ Primer>> 4
th edition <<Effective C++>> 3
th edition <<Design Patterns>> <<Exceptional C++>>