zoukankan      html  css  js  c++  java
  • java/c++钻石问题(菱形继承问题) 虚继承

    看下面的一幅图:

    In the diagram above, we have 2 classes B and C that derive from thesame class – which would be class A in the diagram above. We also have class D that derives from both B and C by using multiple inheritance. You can see in the figure above that the classes essentially form the shape of a diamond – which is why this problem is called the diamond problem.

    The problem with having an inheritance hierarchy like the one shown in the diagram above is that when we instantiate an object of class D, any calls to method definitions in class A will be ambiguous – because it’s not sure whether to call the version of the method derived from class B or class C.

    钻石问题是:当我们实例化D时,试图去调用在A中定义的方法会是模糊的,因为不确定是调用B的方法还是C的。

    Java does not have multiple inheritance

    But, wait one second. Java does not have multiple inheritance! This means that Java isnot at risk of suffering the consequences of the diamond problem. However, C++ does have multiple inheritance, and if you want to read more about the diamond problem in C++, check this out: Diamond problem in C++.

    Java does have interfaces

    Java has interfaces which do allow it to mimic multiple inheritance. Although interfaces give us something similar to multiple inheritance, the implementation of those interfaces is singly (as opposed to multiple) inherited. This means that problems like the diamond problem – in which the compiler is confused as to which method to use – will not occur in Java.java中不存在钻石问题。

    c++砖石问题解决:

    假设我们有类B和类C,它们都继承了相同的类A。另外我们还有类D,类D通过多重继承机制继承了类B和类C。因为上述图表的形状类似于钻石(或者菱形),因此这个问题被形象地称为钻石问题(菱形继承问题)。现在,我们将上面的图表翻译成具体的代码:

    /*
    The Animal class below corresponds to class 
    A in our graphic above
    */
                                    
    class Animal { /* ... */ }; // base class
    {
    int weight;
    
    public:
    
    int getWeight() { return weight;};
    
    };
    
    class Tiger : public Animal { /* ... */ };
    
    class Lion : public Animal { /* ... */ }    
                            
    class Liger : public Tiger, public Lion { /* ... */ };    

    在上面的代码中,我们给出了一个具体的钻石问题例子。Animal类对应于最顶层类(图表中的A),Tiger和Lion分别对应于图表的B和C,Liger类(狮虎兽,即老虎和狮子的杂交种)对应于D。

    现在,问题是如果我们有这种继承结构会出现什么样的问题。

    看看下面的代码后再来回答问题吧

    int main( )
    {
    Liger lg ;
    
    /*编译错误,下面的代码不会被任何C++编译器通过 */ vs报错getWeight对基类访问不明确。
    
    int weight = lg.getWeight();  
    }

    在我们的继承结构中,我们可以看出Tiger和Lion类都继承自Animal基类。所以问题是:因为Liger多重继承了Tiger和Lion类,因此Liger类会有两份Animal类的成员(数据和方法),Liger对象"lg"会包含Animal基类的两个子对象。

    所以,你会问Liger对象有两个Animal基类的子对象会出现什么问题?再看看上面的代码-调用"lg.getWeight()"将会导致一个编译错误。这是因为编译器并不知道是调用Tiger类的getWeight()还是调用Lion类的getWeight()。所以,调用getWeight方法是不明确的,因此不能通过编译。

    Solution to the Diamond Problem

    We’ve given an explanation of the diamond problem, but now we want to give you a solution to the diamond problem. If the inheritance from the Animal class to both the Lion class and the Tiger class is marked as virtual, then C++ will ensure that only one subobject of the Animal class will be created for every Liger object. This is what the code for that would look like:如果Lion类和Tiger类在分别继承Animal类时用virtual来标注,对于每一个Liger对象,C++会保证只有一个Animal类的子对象会被创建.

    class Tiger : virtual public Animal { /* ... */ };
    
    class Lion : virtual public Animal { /* ... */ }
    

    You can see that the only change we’ve made is to add the "virtual" keyword to the Tiger and Lion class declarations. Now the Liger class object will have only one Animal subobject, and the code below will compile just fine:

    int main( )
    {
    Liger lg ;
    
    /*THIS CODE WILL NOW COMPILE OK NOW THAT WE'VE
    USED THE VIRTUAL KEYWORD IN THE TIGER AND LION
    CLASS DECLARATIONS */
    
    int weight = lg.getWeight();  
    }

    我们接下来来深入了解下virtual 继承。

    wikipedia定义:

    虚继承 是面向对象编程中的一种技术。当一个指定的基类,从继承面上来讲,在声明时将其成员实例共享给也从这个基类继承来的其它类。举例来说:假如类A和类B是由类X继承而来(非虚继承且假设类X包含一些成员),且类C同时继承了类AB,那么C就会拥有两套和X相关的成员(可分别独立访问,一般要用适当的消歧义修饰符)。但是如果类A虚继承自类X,那么C将会只包含一组类X的成员数据。对于这一概念应用最好的编程语言是C++

    这一特性在多重继承应用中非常有用,可以使得虚基类对于它所继承的类和所有由它继承而来的类来说变成一个普通的子对象。还可以用于避免由于带有歧义的组合而产生的问题(如“平行四边形问题”)。其原理是通过说明使用了哪一个父类来消除歧义,具体来讲,虚类(V)穿透了其父类(相当于上面例子中的B),相当于它就是B的直接基类而不是通过间接继承的它真正的基类(A)。[1][2]

    这一概念一般用于“继承”在表现为一个整体,而非几个部分的组合时。在C++中,基类可以通过使用关键字virtual来声明虚继承关系。

    http://zh.wikipedia.org/wiki/%E8%99%9A%E7%BB%A7%E6%89%BF#cite_note-2

    via:http://www.programmerinterview.com/index.php/c-cplusplus/diamond-problem/

    c++虚基类:

    参考http://www.learncpp.com/cpp-tutorial/118-virtual-base-classes/

    [cpp] view plaincopy
     
    #include<iostream>
    using namespace std;
    class Base{
    public:
        Base()
        {
            cout<<"Base constructor"<<endl;
        }
    };
    class A:public Base{
    public:
        A()
        {
            cout<<"A constructor"<<endl;
        }
    
    };
    class B: public Base{
    public:
        B()
        {
            cout<<"B constructor"<<endl;
        }
    
    };
    class D:public A,B{
    public:
        D(){
            cout<<"D constructor"<<endl;
    
        }
    
    };
    
    int main(){
        D d;
    }


    程序输出

    Base constructor

    A  constructor

    Base constructor

    B constructor

    D constructor

    A的基类是Base,B的基类也是Base,而类D多继承自A,B,可以看到基类Base的构造函数被调用了两次

    有没有方法可以使基类只调用一次呢,这就需要引入虚基类(virtual base class)的概念

    虚基类只需在派生类的继承列表前加入virtual关键字即可。

    Class A:virtua public Base{

    }

    虚基类只会创建虚基类的一个对象。 (只调用一次基类的构造函数)。虚基类的构造函数由继承层次最深的类调用(if a class inherits one or more classes that have virtual parents, the most derived class is responsible for constructing the virtual base class.)

    如果要识别某类的父类为虚基类,该派生类的继承列表前必须添加virtual关键字。否则将不识别该类的父类为虚基类

    比如下例中若类A或类B中有一个未加virtual关键字,仍将调用基类构造函数两次

    [cpp] view plaincopy
     

     

    #include<iostream>
    using namespace std;
    class Base{
    public:
        Base()
        {
            cout<<"Base constructor"<<endl;
        }
    };
    class A:virtual public Base{
    public:
        A()
        {
            cout<<"A constructor"<<endl;
        }
    
    };
    class B: virtual public Base{
    public:
        B()
        {
            cout<<"B constructor"<<endl;
        }
    
    };
    class D:public A,B{
    public:
        D(){
            cout<<"D constructor"<<endl;
    
        }
    
    };
    
    int main(){
        D d;
        
    }

    以上程序输出

    Base constructor

    A  constructor

    B constructor

    D constructor

    基类构造函数只调用一次,基类构造函数由类D调用

    #include<iostream>
    using namespace std;
    class Base{
    public:
        Base()
        {
            cout<<"Base constructor"<<endl;
        }
    };
    class A:virtual public Base{
    public:
        A()
        {
            cout<<"A constructor"<<endl;
        }
    
    };
    class B: virtual public Base{
    public:
        B()
        {
            cout<<"B constructor"<<endl;
        }
    
    };
    class E:public Base{
    public:
        E()
        {
            cout<<"E constructor"<<endl;
        }
    
    };
    class D:public A,B,E{
    public:
        D(){
            cout<<"D constructor"<<endl;
    
        }
    
    };
    
    int main(){
        D d;
        
    }

      

    以上程序输出

    Base constructor

    A  constructor

    B constructor

    Base constructor

    E constructor

    D constructor

    基类Base构造函数调用了两次,因为派生类E继承列表前未添加virtual关键字,不表明该类E继承自虚基类,仍会调用基类构造函数

     深入:

    多继承:

    http://www.cnblogs.com/cutepig/archive/2009/02/21/1395216.html

    http://www.cppblog.com/chemz/archive/2007/06/12/26135.html

    http://bigasp.com/archives/486

  • 相关阅读:
    跑酷游戏的一些bug总结(滥用FixedUpdate的坑)
    Unity在编辑器状态下清空控制台信息
    Unity脚本在层级面板中的执行顺序测试3
    IronPython使用
    RSA加密的测试demo
    常用加密算法学习
    c#读写ini文件
    Jrebel激活方法(转)
    ThreadLocal Memory Leak in Java web application
    Java Thread Local – How to use and code sample(转)
  • 原文地址:https://www.cnblogs.com/youxin/p/2961219.html
Copyright © 2011-2022 走看看