确定你的public继承塑模出is-a关系

  1. 如果一个派生类以public形式继承基类,那就是说每个派生类对象同时也是一个基类对象,反之则不成立。只要基类能派生用场的地方,派生类就能派上用场。
  2. public继承不改变基类成员的访问权限,子类可以根据基类成员的访问权限进行访问。
  3. is-a并非唯一存在于classes之间的关系,另两个常见关系有has-a(有一个)和is-implemented-in-terms-of(根据某物实现出)。
  4. public继承意味着is-a,适用于基类上的一定适用于派生类,因为每个派生类对象也都是一个基类对象。

避免遮掩继承而来的名称

  1. 子类与父类的函数同名时,子类会覆盖掉父类所有的同名函数,如下例,子类的一个func_0,把父类的func_0和func_0(int)都覆盖掉了。
  2. 发生覆盖时,父类指针指向子类对象时,访问的全部都是父类的同名成员函数(非虚函数)。
  3. 为了解决上述问题,我们可以使用using声明式让基类中的函数在派生类中可见,也可以使用一个转交函数,在转交函数中使用::作用域符直接调用基类中的方法
  4. 转交函数的另一个用途是为那些不支持using声明式的老旧编译器另辟一条新路。
  5. 派生类的名称会遮掩基类中的名称,在public继承下从来没有人希望如此。

区分接口继承和实现继承

  1. 我们有时候会希望派生类只继承基类成员函数的接口(也就是声明),有时候希望派生类同时继承函数的接口和实现,但又希望能够override所继承的实现,有时候希望派生类继承函数的接口和实现,并且不允许override任何东西。
  2. 以public形式继承的派生类总是会继承成员函数的接口。
  3. 纯虚函数有两个最突出的特性:它们必须被任何“继承了它们”的派生类重新声明,而且它们在抽象类中通常没有定义(但是可以为纯虚函数提供定义)。
  4. 声明一个纯虚函数的目的是为了让派生类只继承函数接口。
  5. 声明(非纯)虚函数的目的是让派生类继承该函数的接口和缺省实现。
  6. 声明no-virtual函数的目的是令派生类继承函数的接口和一份强制性实现。

考虑virtual函数以外的其他选择

  1. 有时候我们需要针对不同的个体采取不同的行为,除了virtual函数之外我们还可以采用一些其他的解决方案。
  2. 采用non-virtual interface(NVI)手法,这是template method设计模式的一种特殊形式,它以public non-virtual成员函数包裹较低访问性(private或者protected)的virtual函数。
  3. 将virtual函数替换为“函数指针成员变量”,这是strategy设计模式的一种分解表现形式。
  4. 以std::function成员变量替换virtual函数,因而允许使用任何callable的对象搭配一个兼容于需求的签名式。这也是strategy设计模式的某种形式。
  5. 将继承体系内的virtual函数替换为另一个继承体系内的virtual函数。这是strategy设计模式的传统实现手法。
  6. 将机能从成员函数移到class外部函数,带来的一个缺点是非成员函数无法访问class的non-public成员。
  7. std::function对象的行为就像一般函数指针,这样的对象可接纳“与给定签名式兼容的”所有可调用物。

绝不重新定义继承而来的non-virtual函数

  1. 如果派生类中重新定义了从基类中继承而来的non-virtual函数,而non-virtual函数都是静态绑定的,就算基类指针指向的是一个派生类对象,那么也是调用的基类中定义的版本。而virtual函数则没有这个困扰,因为virtual函数都是动态绑定的。
  2. 当派生类中redefine了基类中的non-virtual函数,当这个函数被调用时,任何一个派生类对象都可能表现出基类或派生类的行为,决定因素不在对象自身,而在于“指向该对象之指针或引用”当初的声明类型。
  3. 任何情况下都不该重新定义一个继承而来的non-virtual函数。

绝不重新定义继承而来的缺省参数值

  1. 由于重新定义一个继承而来的non-virtual函数永远都是错误的,所以我们可以重新定义的只能是继承而来的virtual函数。
  2. virtual函数是动态绑定的,而函数缺省参数值确是静态绑定的。因此调用的virtual虽然是动态类型中的版本,但是函数的缺省参数值确是静态类型中的版本。
  3. 如果缺省参数值动态绑定,编译器就必须有某种办法在运行期为virtual函数决定适当的参数缺省值,这比目前实行的“在编译期决定”的机制更慢而且更复杂。
  4. 如果我们想要提供缺省参数值给virtual函数,我们可以使用NVI(non-virtual interface)方法:令基类内的一个public non-virtual函数调用private virtual函数,后者可被派生类重新定义。我们可以让non-virtual函数指定缺省参数值,而private virtual函数负责真正的工作。

通过符合塑模出has-a或“根据某物实现出”

  1. 复合的意义和public继承完全不同
  2. 在应用域,复合意味着has-a(有一个),在实现域,复合意味着is-implemented-in-terms-of(根据某物实现出)

明智而审慎地使用private继承

  1. 如果classes之间的继承关系是private,编译器不会自动将一个派生类对象转换为一个基类对象。
  2. 由private继承而来的所有基类成员在派生类中都会变成private属性,即使它们本来在基类中是public或protected属性
  3. private继承意味着is-implemented-in-terms-of(根据某物实现出),因为我们只是为了采用基类中的已经备妥的某些特性,而不是基类和派生类存在任何观念上的关系。复合的意义也是根据某物实现出,二者如何取舍:尽可能使用复合,必要时才使用private继承。
  4. 如果我们想阻止派生类重新定义virtual函数,我们可以将这个virtual函数放入基类中的某个private成员中,这样派生类就无法继承它或者重新定义virtual函数了
  5. private继承主要用于“当一个意欲成为派生类的类想访问一个意欲成为基类的protected成分或者为了重新定义一个或多个virtual函数”。
  6. 和复合不同,private继承可以造成empty base最优化,这对致力于“对象尺寸最小化”的程序库开发者而言,可能很重要。

明智而审慎地使用多重继承

  1. 使用多重继承时,当程序从一个及以上的基类继承相同名称(如函数、typedef等等)时,可能会导致较多的歧义机会,因为编译器不知道你要调用哪个基类中的函数
  2. C++用来解析重载函数和继承中的同名函数的规则:在看到是否有个函数可取用之前,C++首先确认这个函数对此调用而言是最佳匹配,找出最佳匹配函数才检验其可取用性(是否是private属性等)。
  3. 多重继承还会导致“钻石型多重继承”(菱形继承),这样造成的问题是是否让基类内的成员变量经由每一条路径被复制,C++默认做法是执行复制,如果我们不想复制就需要使带有这个数据的基类成为一个虚基类,此时需要令所有直接继承自它的classes采用虚继承。
  4. 为了避免菱形继承所带来的问题,正确做法是public继承都应该是virtual,但是使用virtual继承所产生的对象往往比non-virtual继承的对象的体积要大,访问其中的成员变量时速度也会更慢,我们需要为virtual继承付出相应的代价。
  5. 鉴于virtual继承所带来的代价,因此非必要不要使用虚基类,平常使用non-virtual继承。并且如果必须使用虚基类,尽可能避免在其中放置数据,这样一来就不用担心这些classes的初始化和赋值所带来的诡异事情了。
  6. 多重继承的确有正当用途,当其中一个情节涉及“public继承某个interface class”和“private继承某个协助实现的class”的两相结合。

在类的成员函数中调用delete this

  1. 当使用类初始化一个局部的类对象时,由于这个对象是被压入我们的函数栈当中,因此当你delete这个对象的指针,它也还是会存在这个函数栈当中,只有但函数栈回收的时候才会回收这个对象。因此此时就算在类中的成员函数调用delete this,我们也能正常访问这个对象的成员函数和数据成员
  2. 当使用new来初始化一个指向类对象的指针时,指针对应的对象是被分配到堆当中的,当我们delete的时候,系统就可以马上回收这个堆的内容并可能对堆的内容分布做一些优化和调整,这个时候对象对应的内容也就不存在了,因此我们无法正常访问其成员,但是可以正常调用其成员函数(虚函数除外)
  3. 如果在类的析构函数中调用delete this,会导致堆栈溢出,由于delete的本质是“为将被释放的内存调用一个或多个析构函数,然后,释放内存”,delete this会去调用本对象的析构函数,而析构函数中又调用delete this,形成无限递归,造成堆栈溢出,系统崩溃。
  4. 调用delete this只是告诉系统我们不需要这个对象的内存空间了,请求释放它,但并不会主动帮我们把这个指针置为null,它依然指向原来的内存地址