C++继承中关于子类构造函数的写法

转载于:http://www.cnblogs.com/kaige/p/cplusplus_virtual_inheritance_derived_class_constructor.html

构造方法用来初始化类的对象,与父类的其它成员不同,它不能被子类继承(子类可以继承父类所有的成员变量和成员方法,但不继承父类的构造方法)。因此,在创建子类对象时,为了初始化从父类继承来的数据成员,系统需要调用其父类的构造方法。

如果没有显式的构造函数,编译器会给一个默认的构造函数,并且该默认的构造函数仅仅在没有显式地声明构造函数情况下创建。

构造原则如下:

1. 如果子类没有定义构造方法,则调用父类的无参数的构造方法。

2. 如果子类定义了构造方法,不论是无参数还是带参数,在创建子类的对象的时候,首先执行父类无参数的构造方法,然后执行自己的构造方法。

3. 在创建子类对象时候,如果子类的构造函数没有显示调用父类的构造函数,则会调用父类的默认无参构造函数。

4. 在创建子类对象时候,如果子类的构造函数没有显示调用父类的构造函数且父类自己提供了无参构造函数,则会调用父类自己的无参构造函数。

5. 在创建子类对象时候,如果子类的构造函数没有显示调用父类的构造函数且父类只定义了自己的有参构造函数,则会出错(如果父类只有有参数的构造方法,则子类必须显示调用此带参构造方法)。

6. 如果子类调用父类带参数的构造方法,需要用初始化父类成员对象的方式,比如:

 1 #include <iostream.h>
 2
 3   class animal
 4   {
 5   public:
 6     animal(int height, int weight)
 7     {
 8       cout<<"animal construct"<<endl;
 9     }
10     …
11   };
12
13   class fish:public animal
14   {
15   public:
16     fish():animal(400,300)
17     {
18       cout<<"fish construct"<<endl;
19     }
20     …
21   };
22   void main()
23   {
24     fish fh;
25   }

在fish类的构造函数后,加一个冒号(:),然后加上父类的带参数的构造函数。这样,在子类的构造函数被调用时,系统就会去调用父类的带参数的构造函数去构造对象。这种初始化方式,还常用来对类中的常量(const)成员进行初始化,如下面的代码所示:

1 class point
2   {
3   public:
4      point():x(0),y(0)
5   private:
6      const int x;
7      const int y;
8   };

当然,类中普通的成员变量也可以采取此种方式进行初始化,然而,这就没有必要了。

最近工作中某个软件功能出现了退化,追查下来发现是一个类的成员变量没有被正确的初始化。这个问题与C++存在虚继承的情况下派生类构造函数的写法有关。在此说明一下错误发生的原因,希望对更多的人有帮助。

我们代码中存在虚继承的类的继承结构与下图类似,并不是教科书中经典的菱形结构。从 Intermediate1 和 Intermediate3 到Base2 的继承是虚继承。Base1 和 Base2 包含一些成员变量,并提供了相应的构造函数接受指定的初始化值。Base2 还有一个缺省构造函数,把其成员变量都初始化为0。Intermediate1,2,3 也都提供了一个构造函数接受指定的初始化值,并在在初始化列表里调用Base1和Base2的构造函数完成初始化。

一位同事在做重构时,不小心把Final的代码改成了:

 1 class Final : public Intermediate2, public Intermediate3 {
 2 public:
 3     Final (int a, int b, int c)
 4         : Intermediate2(a, b, c),
 5           Intermediate3(b, c)
 6     {
 7
 8     }
 9
10 };
 1 class Intermediate1 : public Base1, virtual public Base2 {
 2 public:
 3     Intermediate1(int a, int b, int c)
 4         : Base1(a),
 5           Base2(b, c)
 6     {
 7
 8     }
 9 };
10
11 class Intermediate2 : public Intermediate1 {
12 public:
13     Intermediate2(int a, int b, int c)
14         : Intermediate1(a, b, c),
15           Base2(b, c)
16     {
17
18     }
19 };
20
21 class Intermediate3 : virtual public Base2 {
22 public:
23     Intermediate3(int b, int c)
24         : Base2(b, c)
25     {
26
27     }
28 };

看上去,Final的构造函数将调用Intermediate2 和 Intermediate3的构造函数分别将m_a, m_b 和 m_c初始化成指定的值。可是,运行时发现m_b和m_c的值是0!明显,这是调用了Base2的缺省构造函数。

原来,C++的规则是:如果在继承链上存在虚继承的基类,则最底层的子类要负责完成该虚基类部分成员的构造。我们可以显式调用虚基类的构造函数完成初始化。如果不显式调用虚基类的构造函数,则编译器会调用虚基类的缺省构造函数。如果不显式调用虚基类的构造函数,而虚基类没有定义缺省构造函数,则会出现编译错误。这条规则的原因是:如果不这样做,则虚基类部分会在存在的多个继承链条上被多次初始化。

很多时候,对于继承链上的中间类,我们也会在其构造函数中显式调用虚基类的构造函数,因为一旦有人要创建这些中间类的对象,我们也要保证它们得到正确的初始化。

所以,如果我们要把m_b和m_c初始化成指定的值,Final的构造函数的正确写法应该是这样:

1 Final (int a, int b, int c)
2     : Base2(b, c),
3       Intermediate2(a, b, c),
4       Intermediate3(b, c)
5 {
6
7 }

完整的测试程序如下所示,有兴趣的同学可以自行编译运行一下。也可以在调试器中单步运行Final的构造函数,看看前后两种写法分别是调用了Base2的哪个构造函数。

 1 #include "stdafx.h"
 2 #include <iostream>
 3
 4 using namespace std;
 5
 6 class Base1 {
 7 public:
 8     Base1(int a): m_a(a) {}
 9
10 protected:
11     int m_a;
12 };
13
14 class Base2 {
15 public:
16     Base2(int b, int c): m_b(b), m_c(c) {}
17     Base2() : m_b(0), m_c(0) {}
18
19 protected:
20     int m_b;
21     int m_c;
22 };
23
24 class Intermediate1 : public Base1, virtual public Base2 {
25 public:
26     Intermediate1(int a, int b, int c)
27         : Base1(a),
28           Base2(b, c)
29     {
30
31     }
32 };
33
34 class Intermediate2 : public Intermediate1 {
35 public:
36     Intermediate2(int a, int b, int c)
37         : Intermediate1(a, b, c),
38           Base2(b, c)
39     {
40
41     }
42 };
43
44 class Intermediate3 : virtual public Base2 {
45 public:
46     Intermediate3(int b, int c)
47         : Base2(b, c)
48     {
49
50     }
51 };
52
53 class Final : public Intermediate2, public Intermediate3 {
54 public:
55     Final (int a, int b, int c)
56         : Base2(b, c),
57           Intermediate2(a, b, c),
58           Intermediate3(b, c)
59     {
60
61     }
62
63     void Print() {
64         cout<<m_a<<", "<<m_b<<", "<<m_c<<endl;
65     }
66 };
67
68
69 int _tmain(int argc, _TCHAR* argv[])
70 {
71     Final finalObj(1, 2, 3);
72     finalObj.Print();
73
74     return 0;
75 }
时间: 2024-10-12 15:42:31

C++继承中关于子类构造函数的写法的相关文章

java4android (继承中的子类实例化过程)

生成子类的过程 见代码: class Person { String name; int age; Person(){ System.out.print("Person的无参数构造函数"); } Person(String name,int age){ this.name = name; this.age = age; System.out.print("Person的有2个参数的构造函数"); } void eat(){ System.out.print(&quo

java构造函数是否可继承,以及子类构造函数可否不使用super调用超类构造函数

问题一:java的构造函数能否被继承? 笔者初学java看的一本书说:“java的子类自然的继承其超类的“非private成员”. 通常java的构造函数被设置为public的(若你不写构造函数,java自动添加的无参空构造函数就是public的),因本文中的类都在同一个包中,因此使用无修饰的友好权限说明问题,对于private构造函数的意义,可参见这里. 那么根据该书所述规则,非private的构造函数当然也应该被子类继承. 但实际上,子类不仅无法继承private成员,也无法继承构造函数.

继承中的的构造函数问题

代码如下: 1 class Program 2 { 3 static void Main(string[] args) 4 { 5 Teacher t1 = new Teacher("许大虾", 23, "[email protected]", 10000); 6 Student s1 = new Student("许大虾", 23, "[email protected]", "100001"); 7 }

C++中继承中遇到的构造函数问题

今天在开发时遇到了一个之前一直以为理所当然的构造函数问题. 先给总结: 子类在构造时,如果没有显式调用父类的构造函数,会先调用父类的默认构造函数(无参数的) 下面给出不同情况的例子 例子一:父类有默认的构造函数,子类的构造函数随便 #include <iostream> class Base{ public: Base(){ } }; class Derive:public Base{ public: Derive(int a,int b){ } }; int main(){ Derive c

5.Java继承中方法的覆盖和重载

在类继承中,子类可以修改从父类继承来的方法,也就是说子类能创建一个与父类方法有不同功能的方法,但具有相同的名称.返回值类型.参数列表. 如果在新类中定义一个方法,其名称.返回值类型和参数列表正好与父类中的相同,那么,新方法被称做覆盖旧方法. 参数列表又叫参数签名,包括参数的类型.参数的个数和参数的顺序,只要有一个不同就叫做参数列表不同. 被覆盖的方法在子类中只能通过super调用. 注意:覆盖不会删除父类中的方法,而是对子类的实例隐藏,暂时不使用. 请看下面的例子: public class D

四. Java继承和多态3. 继承中的方法的覆盖和重载

在类继承中,子类可以修改从父类继承来的方法,也就是说子类能创建一个与父类方法有不同功能的方法,但具有相同的名称.返回值类型.参数列表. 如果在新类中定义一个方法,其名称.返回值类型和参数列表正好与父类中的相同,那么,新方法被称做覆盖旧方法. 参数列表又叫参数签名,包括参数的类型.参数的个数和参数的顺序,只要有一个不同就叫做参数列表不同. 被覆盖的方法在子类中只能通过super调用. 注意:覆盖不会删除父类中的方法,而是对子类的实例隐藏,暂时不使用. 请看下面的例子: public class D

继承中子类构造函数相关问题

Day08_SHJavaTraing_4-13-2017 1.为什么任何一个类(不包含Object)的构造函数中都需要一个super() 语句? 因为除了Object类以外,所有类都会继承一个父类:继承父类,那么子类实例化时就需要给父类中的成员变量显示赋值,就需要用到父类中的构造函数. 2.如果父类中没有无参构造函数,子类如何实例化? super()表示调用父类无参构造函数:如果父类中没有无参构造函数,就会报错. 如何解决这个问题呢? 方法①在父类中添加一个无参构造函数 方法②在子类的构造函数中

继承中的构造函数

构造函数的作用:初始化对象, 继承的两个特性:单根性,和传递性. 一:一个类中可以存在多个构造函数,他们实现一个重载关系.构造函数之间会造成冗余的情况. 列如: 解决办法就是用this来解决冗余问题. this 有两个作用:1.表示当前类的对象.2.解决本类中出现的冗余问题. 二:继承中构造函数. 继承:即子类(派生类)继承父类(基类)的属性和方法,在这个过程中发生的过程: 1.为子类实例化对象时,系统会默认为父类实例化对象,(默认调用的是空构造函数)调用父类的属性和方法,然后才实例化子类对象.

C++继承中析构函数 构造函数的调用顺序以及虚析构函数

首先说说构造函数.大家都知道构造函数里就能够调用成员变量,而继承中子类是把基类的成员变成自己的成员,那么也就是说子类在构造函数里就能够调用基类的成员了,这就说明创建子类的时候必须先调用基类的构造函数,仅仅有这样子类才干在构造函数里使用基类的成员,所以是创建子类时先调用基类的构造函数然后再调用自己的构造函数.通俗点说,你要用某些物品.但这些物品你没办法自己生产,自然就要等别人生产出来,你才干拿来用. 接着就是析构函数了,上面说到子类是将基类的成员变成自己的成员,那么基类就会仅仅存在子类中直到子类调