【ThinkingInC++】75、多重继承

第九章 多重继承

9.2 接口继承

Intertfacees.cpp

/**

* 书本:【ThinkingInC++】

* 功能:接口继承Interfaces.cpp

* 时间:2014年10月28日20:06:31

* 作者:cutter_point

*/

 

#include <iostream>

#include <sstream>

#include <string>

 

using namespace std;

 

class Printable //抽象类

{

public:

   virtual ~Printable() {} //虚函数

   virtual void print(ostream&) const=0;   //纯虚函数

};

 

class Intable

{

public:

   virtual ~Intable() {}

   virtual int toInt() const=0;

};

 

class Stringable

{

public:

   virtual ~Stringable() {}

   virtual string toString() const=0;

};

 

class Able : public Printable, publicIntable, public Stringable

{

   int myData;

public:

   Able(int x) { myData=x; }

   void print(ostream& os) const { os<<myData; }

   int toInt() const { return myData; }

   string toString() const

    {

       ostringstream os;

       os<<myData;

       return os.str();

    }

};

 

void testPrintable(const Printable& p)

{

   p.print(cout);

   cout<<endl;

}

 

void testIntable(const Intable& n)

{

   cout<<n.toInt()+1<<endl;

}

 

void testStringable(const Stringable&s)

{

   cout<<s.toString()+"th"<<endl;

}

 

int main()

{

   Able a(7);

   testPrintable(a);

   testIntable(a);

   testStringable(a);

 

   return 0;

}


 

9.5 虚基类

 

虚拟继承的机制

实际上造成上边的二义性的根本原因是在这种继承的特殊模式下,A这个父类分别伴随BC产生了两个拷贝,在调用拷贝中的方法时产生了矛盾,到底是调用哪一个拷贝中的print()呢?于是,所有人都会想,要是只有一个拷贝就好了,就没有矛盾了,虚拟继承就提供了这种机制,按上面的例子,只需修改BCA的继承方式,即加一个关键字 virtual

class B:  virtual public A

{

    public:

        B(){cout << "B called"<< endl;}

       

    private:

           

};

class C:  virtual public A

{

    public:

        C(){cout << "C called"<< endl;}

           private:

           

};


这样就相当于说,在没有A类的拷贝时就构造一个,如果已经有了,就用已经有的那一个,这样一来,拷贝只有一份了,二义性消除了。

 

static_cast 和 dynamic_cast

 

 

dynamic_cast主要用于类层次间的上行转换和下行转换,还可以用于类之间的交叉转换。

在类层次间进行上行转换时,dynamic_cast和static_cast的效果是一样的;在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全。

 

子对象的初始化顺序遵循如下规则:

1)  所有虚基类子对象,按照他们在类定义中出现的位置,重上到下、从左到右初始化

2)  然后非虚基类按通常顺序初始化

3)  所有的成员对象按声明的顺序初始化

4)  完整的对象的构造函数执行

 

前面讲过,为了初始化基类的子对象,派生类的构造函数要调用基类的构造函数。对于虚基类来讲,由于派生类的对象中只有一个虚基类子对象。为保证虚基类子对象只被初始化一次,这个虚基类构造函数必须只被调用一次。

 

 

 

如果类继承中包括多个虚基类的实例,基类只被初始化一次。

1、如果类里面有成员类,成员类的构造函数优先被调用;

2、创建派生类的对象,基类的构造函数函数优先被调用(也优先于派生类里的成员类);

3、 基类构造函数如果有多个基类则构造函数的调用顺序是某类在类派生表中出现的
顺序而不是它们在成员初始化表中的顺序;


4、成员类对象构造函数如果有多个成员类对象则构造函数的调用顺序是对象在类中
被声明的顺序而不是它们出现在成员初始化表中的顺序;


5、派生类构造函数
作为一般规则派生类构造函数应该不能直接向一个基类数据成员赋值而是把值传递
给适当的基类构造函数否则两个类的实现变成紧耦合的(tightly coupled)将更加难于
正确地修改或扩展基类的实现。(基类设计者的责任是提供一组适当的基类构造函数)

 

 

VirtInit.cpp

//关于虚基类的初始化问题

 

/**
* 书本:【ThinkingInC++】
* 功能:关于虚基类的初始化问题
* 时间:2014年10月28日20:07:25
* 作者:cutter_point
*/

#include <iostream>
#include <string>

using namespace std;

class M
{
public:
    M(const string& s) { cout<<"M "<<s<<endl; } //每一个类都有嵌入的M类型成员
};

class A
{
    M m;    //这里是一个类的组合
public:
    A(const string& s) : m("in A")
    {
        cout<<"A "<<s<<endl;    //跟踪类A的初始化
    }
    virtual ~A() { cout<<"析构A"<<endl; } //这是一个虚基类
};

class B
{
    M m;    //这里是一个类的组合
public:
    B(const string& s) : m("in B")
    {
        cout<<"B "<<s<<endl;    //跟踪类A的初始化
    }
    virtual ~B() { cout<<"析构B"<<endl; } //这是一个虚基类
};

class C
{
    M m;    //这里是一个类的组合
public:
    C(const string& s) : m("in C")
    {
        cout<<"C "<<s<<endl;    //跟踪类A的初始化
    }
    virtual ~C() { cout<<"析构C"<<endl; } //这是一个虚基类
};


class D
{
    M m;    //这里是一个类的组合
public:
    D(const string& s) : m("in D")
    {
        cout<<"D "<<s<<endl;    //跟踪类A的初始化
    }
    virtual ~D() { cout<<"析构D"<<endl; } //这是一个虚基类
};

class F : virtual public B, virtual public C, public D  //虚继承
{
    M m;
public:
    F(const string& s) : B("from F"), C("from F"), D("from F"), m("in F")
    {
        cout<<"F "<<s<<endl;
    }
};

//开始多重继承
class E : public A, virtual public B, virtual public C  //虚继承
{
    M m;
public:
    E(const string& s) : A("from E"), B("from E"), C("from E"), m("in E")
    {
        cout<<"E "<<s<<endl;
    }
};



//最终的继承E,F
class G : public E, public F
{
    M m;
public:
    //这里初始化的顺序和继承的顺序不同,看看结果,结果是按继承的顺序初始化
    G(const string& s) : B("from G"), C("from G"), F("from G"), E("from G"), m("in G")
    {
        cout<<"G "<<s<<endl;
    }
};

int main()
{
    //构造函数的调用顺序是某类在类派生表中出现的顺序

    G g("from main");

    return 0;
}


重要结论:

基类构造函数如果有多个基类则构造函数的调用顺序是某类在类派生表中出现的
顺序而不是它们在成员初始化表中的顺序。

 

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。