C++虚基类构造函数详解(调用顺序)之一

  • 解释某个函数,我通常的讲解不会先去长篇大论去空谈,先整个例子来看看!!走起....
#include <iostream>
#include <string>
using namespace std;

class A
{
public:
    A(const char*s)
    {
        cout<<s<<endl;
    }
};
class B:virtual public A
{
public:
    B(const char*s1,const char*s2):A(s1)
    {
        cout <<s2<<endl;
    }
};

class C:virtual public A
{
public:
    C(const char*s1,const char*s2):A(s1)
    {
        cout<<s2<<endl;
    }
};

class D:public B,C
{
public:
    D(const char *s1,const char *s2,const char*s3,const char*s4):B(s1,s2),C(s1,s3),A(s1)
    {
        cout <<s4<<endl;
    }
};
int main(int argc, char* argv[])
{
    D *ptr = new D("class A","class B","class C","class D");
    delete ptr;
    ptr = NULL;
    return 0;
}

先不要忙着去执行代码!!

来看几个基本概念:

一、虚基类的作用:

当一个类的部分或者全部基类来自另一个共同的基类时,这些直接基类中从上一级共同基类继承来的  就拥有相同的名称。在派生类的对象中,这些同名数据成员在内存中同时拥有多个拷贝,同一个函数名会有多个映射。我们可以使用作用域分蝙蝠来唯一标识并分别访问他们, 也可以将共同基类设置为虚基类,这时从不同的路径继承过来的同名数据成员在内存中就只用一个拷贝,同一个函数名也只有一个映射。

二、虚基类的声明  语法形式:

class 派生类名:virtual  继承方式  基类名

三、使用虚基类时应该注意:

1>一个类可以在一个类族中用作虚基类,也可以用作非虚基类。

2>在派生类的对象中,同名的虚基类只产生一个虚基类子对象,而某个非虚基类产生各自的对象。

3>虚基类子对象是由最派生类(最后派生出来的类)的构造函数通过调用虚基类的构造函数进行初始化。

4>最派生类是指在继承类结构中建立对象时所指定的类。

5>在派生类的构造函数的成员初始化列表中,必须列出对虚基类构造函数的调用,如果没有列出,则表示使用该虚基类的缺省构造函数。

6>在虚基类直接或间接派生的派生类中的构造函数的成员初始化列表中,都要列出对虚基类构造函数的调用。但只有用于建立对象的最派生类的构造函数调用虚基类的构造函数,而该派生类的所有基类中列出的对虚基类构造函数的调用在执行中被忽略,从而保证对虚基类子对象只初始化一次。

7>在一个成员初始化列表中,同时出现对虚基类和非虚基类构造函数的调用时,基类的构造函数先于非虚基类的构造函数执行。

8>虚基类并不是在声明基类时声明的,而是在声明派生类是,指定继承方式时声明的。因为一个基类可以在生成一个派生类作为虚基类,而在生成另一个派生类时不作为虚基类。

温馨提示:使用多重继承时要十分小心,经常会出现二义性。许多专业人员认为:不要提倡在程序中使用多重继承,只有在比较简单和不易出现二义性的情况或是在必要时才使用多重继承,能用单一继承解决的问题就不要使用多重继承,也是由于这个原因,有些面向对象的程序设计语言,并不支持多重继承。

现在对虚基类构造函数了解了没??如果还不了解那么咱们就继续深入研究.....

首先,要知道虚拟继承与普通继承的区别:

假设derived继承自base类,那么derived与base是一种“is a”的关系,即derived类是base类,而反之错误;

假设derived虚继承自base类,那么derived与base是一种“has a”的关系,即derived类有一个指向base类的vptr。

因此虚继承可以认为不是一种继承关系,而可以认为是一种组合的关系。因为虚继承有着“继承”两个关键字,那么大部分人都认为虚继承与普通继承的用法没有什么太大的不同,由此用在继承体系中,这种将虚继承认为是普通继承的危害更加大!先用一个例子来说明问题:

#include <iostream>
using namespace std;

class base
{
public:
    base()
    {
        cout <<"base::base()!"<<endl;
    }
    void printBase()
    {
        cout<<"base::printBase()!"<<endl;
    }
};

class derived:public base
{
public:
    derived()
    {
        cout<<"derived::derived()!"<<endl;
    }
    void printDerived()
    {
        cout<<"derived::printDerived()!"<<endl;
    }
};

int main(int argc, char* argv[])
{

    derived oo;
    base oo1(static_cast<base>(oo));

    oo1.printBase();

    cout <<"---------------------"<<endl;
    derived oo2= static_cast<derived&>(oo1);
    oo2.printDerived();
}

运行结果:

对前面的例子稍加修改......................

#include <iostream>
using namespace std;

class base1
{
public:
    base1()
    {
        cout<<"base::base()!"<<endl;
    }
    void printBase()
    {
        cout<<"base::printBase()!"<<endl;
    }
};
class derived1:virtual public base1
{
public:
    derived1()
    {
        cout<<"derived::derived()!"<<endl;
    }
    void printDerived()
    {
        cout <<"derived::printDerived()!"<<endl ;
    }

};

int main(int argc, char* argv[])
{
    derived1 oo;
    base1 oo1(static_cast<base1>(oo));
    oo1.printBase();
    
    derived1 oo2 = static_cast<derived1&>(oo1);
    oo2.printDerived();
    return 0;
}

 

会发现编译错误:error C2635: cannot convert a 'base1*' to a 'derived1*'; conversion from a virtual base class is implied(代码中红色部分出错)

可以看到不能将基类通过static_cast转换为继承类。我们知道c++提供的强制转换函数static_cast对于继承体系中的类对象的转换一般是可行的。那么这里为什么不可以呢??

virtual base class的原始模型是在class object中为每一个有关联的virtual base class加上一个指针vptr,该指针指向virtual基类表。有的编译器是在继承类已存在的virtual table直接扩充导入一个virtual base class table。不管怎么样由于虚继承已完全破坏了继承体系,不能按照平常的继承体系来进行类型转换。

 

  • 我们清楚了虚基类构造函数是怎么回事,那么接下来讲解一下 虚基类构造函数调用顺序 !!

我们下来了解虚拟继承中遇到最广泛的菱形结构:

#include <iostream>
using namespace std;

class stream
{
public:
    stream()
    {
        cout <<"stream::stream()!"<<endl;
    }
};


class iistream:virtual stream
{
public:
    iistream()
    {
        cout <<"istream::istream()!"<<endl;
    }
};

class oostream:virtual stream
{
public:
    oostream()
    {
        cout <<"ostream::ostream()!"<<endl;
    }
};

class iiostream:public iistream,oostream
{
public:
    iiostream()
    {
        cout<<"iiostream::iiostream()!"<<endl;
    }
};

int main(int argc, char* argv[])
{
    iiostream oo;
    return 0;
}

 

运行结果:

本来虚拟继承的目的就是当多重继承出现重复的基类时,其只保存一份基类,减少内存开销。

 

这样子的菱形结构,使公共基类只产生一个拷贝。

从基类stream派生新类时,使用virtual将类stream说明为虚基类,这时派生类istream、ostream包含一个指向虚基类的vptr,而不会产生实际的stream空间。所以最终iiostream也含有一个指向虚基类的vptr,调用stream中的成员方法时,通过vptr去调用,不会产生二义性!

现在我们换种方式使用虚继承:

#include <iostream>
using namespace std;

class stream
{
public:
    stream()
    {
        cout <<"stream::stream()!"<<endl;
    }
};


class iistream:public stream
{
public:
    iistream()
    {
        cout <<"istream::istream()!"<<endl;
    }
};

class oostream:public stream
{
public:
    oostream()
    {
        cout <<"ostream::ostream()!"<<endl;
    }
};

class iiostream:virtual iistream,oostream
{
public:
    iiostream()
    {
        cout<<"iiostream::iiostream()!"<<endl;
    }
};

int main(int argc, char* argv[])
{
    iiostream oo;
    return 0;
}

运行结果:

从结果上可以看到,其构造过程中重复出现基类的stream的构造过程,这样就完全没有达到虚拟继承的目的。其继承结构为:

从继承结构可以看出,如果iiostream对象调用基类stream重的成员方法,会导致方法的二义性。因为iiostream含有指向其虚继承基类istream,ostream的vptr。而istream,ostream包含了stream的空间,所以导致iiostream不知道导致时调用那个stream的方法。要解决该问题,即在调用成员方法时需要加上作用域!

  • 23
    点赞
  • 58
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
这是DS小龙哥编写整理的C++入门指南PDF文档,适合C++初学者,C语言转C++工程师当做入门工具书学习。PDF里有完整示例、知识讲解,平时开发都可以复制粘贴,非常便捷。 目前一共写了7章,后续会持续更新资源包,更新后重新下载即可。 这是目前书籍的目录: C++入门指南 1 一、 C++语言基本介绍与开发环境搭建 1 1.1 C++简介 1 1.2 面向对象编程 1 1.3 Windows系统下搭建C++学习环境 2 二、C++基础入门 16 2.1 C++类和对象 17 2.2 C++命名空间 18 2.3 std标准命名空间 20 2.4 C++新增的标准输入输出方法(cin和cout) 22 2.5 C++规定的变量定义位置 24 2.6 C++新增的布尔类型(bool) 24 2.7 C++ 新增的new和delete运算符 25 2.8 C++函数的默认参数(缺省参数) 26 2.9 C++函数重载详解 28 2.10 C++新增的引用语法 30 三、 C++面向对象:类和对象 34 3.1 类的定义和对象的创建 34 3.2 类的成员变量和成员函数 36 3.3 类成员的访问权限以及类的封装 38 3.4 C++类的构造函数与析构函数 39 3.5 对象数组 47 3.6 this指针 50 3.7 static静态成员变量 52 3.8 static静态成员函数 53 3.9 const成员变量和成员函数 55 3.10 const对象(常对象) 56 3.11 友元函数和友元类 58 3.11.3 友元类 61 3.12 C++字符串 62 四、C++面向对象:继承与派生 75 4.1 继承与派生概念介绍 75 4.2 继承的语法介绍 75 4.3 继承方式介绍(继承的权限) 76 4.4 继承时变量与函数名字遮蔽问题 79 4.5 基类和派生类的构造函数 82 4.6 基类和派生类的析构函数 83 4.7 多继承 85 4.8 继承和虚基类 88 五、C++多态与抽象类 91 5.1 多态概念介绍 91 5.2 函数 92 5.3 纯函数和抽象类 95 六、C++运算符重载 97 6.1 运算符重载语法介绍 97 6.2 可重载运算符与不可重载运算符 98 6.3 一元运算符重载 99 6.4 二元运算符重载 102 6.5 关系运算符重载 104 6.6 输入/输出运算符重载(>>、<<) 105 6.7 函数调用运算符 () 重载 106 6.8 重载[ ](下标运算符) 107 七、C++模板和泛型程序设计 108 7.1 函数模板 108 7.2 类模板 110

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值