INTERESTING AND OBSCURE INHERITANCE ISSUES WITH CPP

1. using 关键字

使用 using 关键字,可以将父类中被隐藏的函数暴露在子类中,但是需要注意的是,在相同情况下,子类函数的优先级更高。

image

image

2.  继承构造函数(C++11)

在c++11之前,构造函数、析构函数、赋值操作符,这些都不能被继承。但是,C++11允许我们使用 using 关键字来继承基类的构造函数。

示例1

image

示例2

image

3. 重写方法时的特殊情况

1) static 超类方法

在C++中,无法重写静态方法,因为方法不可能既是静态的又是虚的。如果子类中存在的静态方法与超类中的静态方法同名,实际上这是两个独立的方法。

2) 重写 private 或者 protected 超类方法

完全没有问题。记住:子类无法调用父类的 private 方法并不意味着无法重写这个方法。

4. 超类方法具有默认参数

子类与超类可以具有不同的默认参数,但使用的参数取决于声明的变量类型,而不是底层的对象。也就是说,C++根据描述对象的表达式类型绑定默认参数,而不是根据实际的对象类型参数。来看下面例子,使用的即为超类方法的默认参数。

image

再来看一个例子:

image

5. 子类方法具有不同的访问级别

我们可以修改父类方法在子类中的访问级别——可以加强限制也可以放宽限制。在 C++ 中,无论是加强限制还是放宽限制,意义都不是很大,但是这么做也是有合理原因的。

为了加强某个方法(或者数据成员)的限制,有两种方法。方法之一是修改整个子类的访问说明符,本文后面将讲述该方法。另一种方法是在子类中重新定义访问限制。

image

我们没有很好的方法(也没有很好的理由)来限制访问父类的 public 方法。

实际上,在子类中放宽访问限制是比较容易,也相对更有意义的。最简答的方法是在子类中提供一个 public 方法来访问父类的 protected 方法,如下所示:

image

调用 Blabber 对象的 public tell() 方法的用户代码可以有效地访问 Secret 类的 protected() 方法。当然,这并没有真正改变 dontTell() 的访问级别,只是提供了访问这个方法的 public 方法,对外提供了一个接口而已。

实际上,我们可以在 Blabber 子类中显式地重写 dontTell(),并将这个方法设置为 public 访问。来看以下代码:

image

以上代码如果通过 blabber 来调用 dontTell() 当然没有问题,但是由于父类中的 protected 方法仍然是 protected 的,因此使用指针或者引用来调用 Secret 的 dontTell() 将无法通过编译。

注意:在工程中唯一有益的,是对父类中的 protected 方法提供较为宽松的访问限制,也就是放宽限制。

6. 子类中的复制构造函数以及赋值运算符

当我们需要在类中动态分配内存时,提供一个  copy constructor 和 assignment operator 是必要的。当定义子类时,我们需要特别关注 copy constructors 以及 operatpr=。

无论父类是否定义了非默认的拷贝构造函数以及复制赋值运算符,只要子类没有特殊的数据成员(通常是指针)需要我们定义一个非默认的拷贝构造函数或者复制赋值运算符,那么我们就无需定义。如果子类省略了 copy constructor 或者 operator=,那么编译器会为子类中的数据成员提供默认的 copy constructor 以及 operator=,同时父类中的 copy constructor 和 operator= 会被用于父类中的数据成员。

另一方面,如果在子类中自定义了 copy constructor,我们就需要在该 copy constructor 中显式地调用父类的 copy constructor。 如果不这么做,那么默认构造函数(不是拷贝构造函数)将被用于对象中的父类成分。

[Why?我个人是这样理解的,因为通常一个类中数据成员都是私有的,那么只有父类的构造函数(无论是默认构造函数还是拷贝构造函数)才能对这些 private 数据成员进行初始化,为了统一,cpp便规定子类中的构造函数必须调用父类的构造函数来对父类部分的数据成员进行初始化。]

image

相似的,如果子类需要重写 operator=,调用父类版本的 operator= 往往是必要的([事实上,对于非 private 数据成员即使不调用父类的 operator= 来完成赋值,语法上也没有错,这点不像拷贝构造函数那样严格])。如果不这样做,可能处于某些很奇怪的原因,使得你只想对 object 的部分数据成员进行赋值。下面的代码示范了如何在子类中调用父类的operator=。

image

If your subclass does not specify its own copy constructor or operator=, the
parent functionality continues to work. If the subclass does provide its own copy
constructor or operator=, it needs to explicitly reference the parent versions.

如果想禁止复制一个类,应该怎么办?显然我们可以把类的复制构造函数设为private,但是这样一来该类的 friend 成员仍然可以复制该类,于是我们只声明这个函数,而不去实现。另外,如果你不需要复制该类的对象,最好把赋值运算也一并禁用掉。

所以这里的做法是:把复制构造函数和赋值运算符的声明设为 private 而不去实现。

01

实际上,更通用的做法是写一个类noncopyable,凡是继承该类的任何类都无法复制和赋值。Why?子类想要定义拷贝构造函数以及赋值运算符,却发现没有办法调用父类的拷贝构造函数以及赋值运算符。

7. The Need for virtual Destructors

无论如何,我们一定要将析构函数设置为 virtual,因为不这么做,很容易导致内存泄漏。来看以下例子:

image

Unless you have a specific reason not to, we highly recommend making all
methods, including destructors but not constructors, virtual. Constructors
cannot and need not be virtual because you always specify the exact class
being constructed when creating an object.

当然,将赋值运算符设置为 virtual 意义并不大,因为父类引用或者指针只能管到自己本身的数据成员。

原文地址:https://www.cnblogs.com/jianxinzhou/p/4403544.html