问:用一句话说明什么是虚拟基类?
我今天参加专业招聘会时一家公司的考官这样问我的,我心里当然知道是多承继承设计类时为了节省派生类占用的空间而涉及的一个概念。本来,我想用笔在纸上画个具有“菱形”的4个类的继承关系的图来辅助说明虚拟基类的概念,可是考官不同意给我笔和纸用,分明是考我的语言表达能力,呵呵。于是我首先跟他提及多承继承,他却表示我的回答根本不切题,并且他还表示不管单一继承还是多重继承都会遇到虚拟基类,关于这点我不敢苟同,当场表示不同意他的看法。有那位C++高手判断一下是我的思路对,还是他的观点对呢?并且这样用一句话说明虚拟基类的定义。谢谢你发表您的高见。

解决方案 »

  1.   

    问题的焦点在于:在单一继承机制下,用OOP设计类的层次时有没有可能考虑采用虚拟基类呢?
      

  2.   

    To arvid_gs(west) :一种什么也干不了,只能用来继承的类不仅仅是虚拟基类,而且还可能是抽象基类。所以你对虚拟基类的一句话概括恐怕要修正了。^_^
      

  3.   

    既然谈到这个问题,就说清楚点吧:
    先举个例子(说明声明虚基类的必要性):
    class base
    {
    public:int i;
    }
    class derived1:public base{
    public:int j;
    }
    class derived2:public base{
    public:int k;
    }
    class derived3:public derived1,public derived2{
    public:int sum;
    }main(void){
    derived3 ob;
    ob.i=10;//问题就在这。因为derived3同时继承了derived1和derived2,而这两个类都继承      base,这就意味着会有两个base拷贝出现在derived3的对象ob中。所以ob对象中会有两个i,那么ob.i=10中的i是derived1对象中的,还是derived2对象中的,程序不知道。所以这样写是不对的,应该写成:bo.derived1::i=10或bo.derived2::i=10;ob.j=20;
    ob.k=30;
    ob.sum=ob.i+ob.j+ob.k;
    count<<"ob.i"<<"ob.j"<<"ob.k"<<"ob.sum";
    }
    我想大家都不想在你的程序中出现这种情况吧,还好C++提供了虚基类。只要在继承基类时,在基类前加上关键字:virtual就可以了。这样前面的问题就解决了,在对象derived3不会出现两个基类的拷贝;修改如下:
    class derived1:virtual public base{
    public:int j;
    }
    class derived2:virtual public base{
    public:int k;
    }
    通常,基类和虚基类之间的唯一区别,体现在对象不止一次继承基类的情况下,即如果使用虚基类,那么对象中只出现一个基类,否则,就会出现多个拷贝。
      

  4.   

    谢谢大家热情的参与,现在问题之一是:在单一继承机制下,用OOP设计类的层次时有没有可能考虑采用虚拟基类呢,比如设计三个类A、B和C,它们之间的关系为A直接派生出B,而B又直接派生出C,我认为在这种单一继承机制下压根就无需把基类定义成虚拟的,因为此时并没有产生继承时的二义性(而考官的意思是在这种单一继承机制下也可能采用虚拟基类,我对此嗤之以鼻^_^);另一方面,在 hardstudylulin(思过崖) 所列举的多重继承的例子中才考虑虚拟基类。不知道我的观点对不对?
      

  5.   

    看了hardstudylulin(思过崖)举的例子,好象你说的对啦,求哪位高人指点
    楼主的错误,我也学习学习.
      

  6.   

    没有多重继承哪里来的 virtual base class 啊。我严重怀疑考官自己没有搞清楚概念。是不是他他 virtual base class 和 base class with pure virutal method 混为一谈了?
      

  7.   

    或许用COM来解释就好理解多了^_^
      

  8.   

    多重继承?
    是指A类同时继承B类和C类?
    还是指B继承A,然后C继承B??
      

  9.   

    To skinny(冷虚空) :
    "多重继承"是指A类同时继承B类和C类,属于树型数据结构;指B继承A,然后C继承B的是"多级单一继承",属于线性数据结构。
      

  10.   

    Virtual base classes offer a way to save space and avoid ambiguities in class hierarchies that use multiple inheritance.
       ---quote from MSDN虚基类提供了一种节省空间的方法,并可在使用多重继承性中避免产生二义性。
      --translate by LeighSword
      

  11.   

    觉得hardstudylulin(思过崖)讲得很清楚的了
      

  12.   

    有如此定义的virtual xxx(xx)=0;的就是
      

  13.   

    多谢大家的参与,看来是见仁见智啊!
    兄弟的系统前几天重装了,VC++6的IDE也装好了,可是现在缺少MSDN 2001版安装程序,有MSDN 2001版安装程序的哪位哥们能帮忙开个FTP,让我下载一下,本人心情火急,多谢!本人答应一定另开贴高分相送!
      

  14.   

    支持搂主,我认为搂住的的观点是正确的!考官hardstudylulin(思过崖),LeighSword(Sword) ,fvv(守护者) 讲得都很明白,学到知识~~~~~~~
      

  15.   

    学过COM编程的人就会明白了,其实我觉得就是一个接口,虚基类什么也不做,具体实现部分由接口对应的实现类(可以叫组件吧,我也学术不精)来实现
      

  16.   

    单继承也可以用虚基类,如下:情况1:
    类:A,B
    class A
    {
    public:
    void f(){printf("A\n");};
    }class B : public A
    {
    public:
    void f(){printf("B\n");};
    }A* a 
    B b;a = &b;
    a->f();
    运行一下看结果是什么,然后在运行下面的:情况2:
    类:A,B
    class A
    {
    public:
    virtual void f(){printf("A\n");};
    }class B : public A
    {
    public:
    void f(){printf("B\n");};
    }A* a 
    B b;a = &b;
    a->f();将这两种情况都运行过了,再去回想你面试的问题。
      

  17.   

    FT!建议某些朋友首先搞清楚下列三个概念的区别,千万不可混为一谈!
    (1)包含纯虚拟函数的基类——抽象类(abstract class),比如class Animal {virtual void IntroduceSelf(void)=0;};抽象类Animal的纯虚拟函数IntroduceSelf仅仅定义了一个接口,只能且必须在所有派生类中实现该函数。比如class Dog {virtual IntroduceSelf(void){cout<<"My name is AHuang!";}};class Cat {virtual IntroduceSelf(void){cout<<"My name is MaoMi!";}};
    (2)包含非纯虚拟函数的基类,该非纯虚拟函数在基类中已经实现了,所以并不要求必须在派生类中实现。这也是它与抽象类的根本区别。
    (3)虚拟基类(virtual base class),我认为只有在多重继承时才有实际意义,目前在单一继承下不可能需要把基类定义成虚拟的。比如,class Commodity{};class MechanismCommodity:virtual public Commodity{};class ElectronicCommodity:virtual public Commodity{};class Automobile:public MechanismCommodity,public ElectronicCommodity{};如果不把基类Commodity定义成虚拟的,则其间接派生类Automobile则既浪费存储空间,又造成潜在的二义性问题。具体理由请参阅hardstudylulin(思过崖) 的例子。 ^_^
      

  18.   

    补充一下,我的上个帖子中(1)列举的例子漏了一些代码,修正如下:class Dog:public Animal {virtual IntroduceSelf(void){cout<<"My name is AHuang!";}};class Cat:public Animal {virtual IntroduceSelf(void){cout<<"My name is MaoMi!";}};
      

  19.   

    虚基类    在《多继承》中讲过的例子中,由类A,类B1和类B2以及类C组成了类继承的层次结构。在该结构中,类C的对象将包含两个类A的子对象。由于类A是派生类C两条继承路径上的一个公共基类,那么这个公共基类将在派生类的对象中产生多个基类子对象。如果要想使这个公共基类在派生类中只产生一个基类子对象,则必须将这个基类设定为虚基类。    虚基类的引入和说明    前面简单地介绍了要引进虚基类的原因。实际上,引进虚基类的真正目的是为了解决二义性问题。虚基类说明格式如下:    virtual <继承方式><基类名>其中,virtual是虚类的关键字。虚基类的说明是用在定义派生类时,写在派生类名的后面。例如:class A
    {
    public:
        void f();
    protected:
        int a;
    };
    class B : virtual public A
    {
    protected:
        int b;
    };
    class C : virtual public A
    {
    protected:
        int c:
    };
    class D : public B, public C
    {
    public:
        int g();
    private:
        int d;
    };由于使用了虚基类,使得类A,类B,类C和类D之间关系用DAG图示法表示如下:                A{ f(), a }
                      /     \
                     B{b}  C{c}
                      \     /
                     D{g(),d}    从该图中可见不同继承路径的虚基类子对象被合并成为一个对象。这便是虚基类的作用,这样将消除了合并之前可能出现的二义性。这时,在类D的对象中只存在一个类A的对象。因此,下面的引用都是正确的:D n;
    n.f(); //对f()引用是正确的。
    void D::g()
    {
        f(); //对f()引用是正确的。
    }下面程序段是正确的。D n;
    A *pa;
    pa = &n;    其中,pa是指向类A对象的指针,n是类D的一个对象,&n是n对象的地址。pa=&n是让pa指针指向类D的对象,这是正确的,并且也无二义性。虚基类的构造函数    前面讲过,为了初始化基类的子对象,派生类的构造函数要调用基类的构造函数。对于虚基类来讲,由于派生类的对象中只有一个虚基类子对象。为保证虚基类子对象只被初始化一次,这个虚基类构造函数必须只被调用一次。由于继承结构的层次可能很深,规定将在建立对象时所指定的类称为最派生类。C++规定,虚基类子对象是由最派生类的构造函数通过调用虚基类的构造函数进行初始化的。如果一个派生类有一个直接或间接的虚基类,那么派生类的构造函数的成员初始列表中必须列出对虚基类构造函数的调用。如果未被列出,则表示使用该虚基类的缺省构造函数来初始化派生类对象中的虚基类子对象。    从虚基类直接或间接继承的派生类中的构造函数的成员初始化列表中都要列出这个虚基类构造函数 的调用。但是,只有用于建立对象的那个最派生类的构造函数调用虚基类的构造函数,而该派生类的基类中所列出的对这个虚基类的构造函数调用在执行中被忽略,这样便保证了对虚基类的对象只初始化一次。    C++又规定,在一个成员初始化列表中出现对虚基类和非虚基类构造函数的调用,则虚基类的构造函数先于非虚基类的构造函数的执行。下面举一例子说明具有虚基类的派生类的构造函数的用法。#include <iostream.h>
    class A
    {
    public:
        A(const char *s) { cout<<s<<endl; }
        ~A() {}
    };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, public 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;
        }
    };void main()
    {
        D *ptr = new D("class A", "class B", "class C", "class D");
        delete ptr;
    }该程序的输出结果为:    class A
        class B
        class C
        class D    在派生类B和C中使用了虚基类,使得建立的D类对象只有一个虚基类子对象。    在派生类B,C,D的构造函数的成员初始化列表中都包含了对虚基类A的构造函数。    在建立类D对象时,只有类D的构造函数的成员初始化列表中列出的虚基类构造函数被调用,并且仅调用一次,而类D基类的构造函数的成员初始化列表中列出的虚基类构造函数不被执行。这一点将从该程序的输出结果可以看出。2001-8-21 23:57          上一讲 | 返回 | 下一讲
    --------------------------------------------------------------------------------
    纯虚函数和抽象类
        纯虚函数是一种特殊的虚函数,它的一般格式如下:class <类名>
    {
        virtual <类型><函数名>(<参数表>)=0;
        …
    };    在许多情况下,在基类中不能对虚函数给出有意义有实现,而把它说明为纯虚函数,它的实现留给该基类的派生类去做。这就是纯虚函数的作用。下面给出一个纯虚函数的例子。#include <iostream.h>class point
    {
    public:
        point(int i=0, int j=0) { x0=i; y0=j; }
        virtual void set() = 0;
        virtual void draw() = 0;
    protected:
        int x0, y0;
    };class line : public point
    {
    public:
        line(int i=0, int j=0, int m=0, int n=0):point(i, j)
        {
        x1=m; y1=n;
        }
        void set() { cout<<"line::set() called.\n"; }
        void draw() { cout<<"line::draw() called.\n"; }
    protected:
        int x1, y1;
    };class ellipse : public point
    {
    public:
        ellipse(int i=0, int j=0, int p=0, int q=0):point(i, j)
        {
        x2=p; y2=q;
        }
        void set() { cout<<"ellipse::set() called.\n"; }
        void draw() { cout<<"ellipse::draw() called.\n"; }
    protected:
        int x2, y2;
    };void drawobj(point *p)
    {
        p->draw();
    }void setobj(point *p)
    {
        p->set();
    }void main()
    {
        line *lineobj = new line;
        ellipse *elliobj = new ellipse;
        drawobj(lineobj);
        drawobj(elliobj);
        cout<<endl;
        setobj(lineobj);
        setobj(elliobj);
        cout<<"\nRedraw the object...\n";
        drawobj(lineobj);
        drawobj(elliobj);
    }抽象类    带有纯虚函数的类称为抽象类。抽象类是一种特殊的类,它是为了抽象和设计的目的而建立的,它处于继承层次结构的较上层。抽象类是不能定义对象的,在实际中为了强调一个类是抽象类,可将该类的构造函数说明为保护的访问控制权限。    抽象类的主要作用是将有关的组织在一个继承层次结构中,由它来为它们提供一个公共的根,相关的子类是从这个根派生出来的。    抽象类刻画了一组子类的操作接口的通用语义,这些语义也传给子类。一般而言,抽象类只描述这组子类共同的操作接口,而完整的实现留给子类。    抽象类只能作为基类来使用,其纯虚函数的实现由派生类给出。如果派生类没有重新定义纯虚函数,而派生类只是继承基类的纯虚函数,则这个派生类仍然还是一个抽象类。如果派生类中给出了基类纯虚函数的实现,则该派生类就不再是抽象类了,它是一个可以建立对象的具体类了。2001-9-14 16:34          上一讲 | 返回 | 下一讲--------------------------------------------------------------------------------
                 ----摘自《C++面向对象程序设计基础教程》不要把“抽象类”和“虚基类”搞混淆了
      

  20.   

    版主要MSDN,网上有吧。我就在网上下载的。
    2003都出来了,要2001干什么,不过我用过2003,加入.net的东西太多了
      

  21.   

    我靠。
    一个语言如果存在这么多复杂难辨的细节,这绝对是它的缺点。
    如果它的这些方面成为某些人玩技术游戏的工具,那更是悲哀。
    除了多重继承,我看不出使用虚拟基类的必要性,而多重继承本身,也并非是一个优秀oop语言的必须因素。
      

  22.   

    “下面程序段是正确的。
    D n;
    A *pa;
    pa = &n;”    其中,pa是指向类A对象的指针,n是类D的一个对象,&n是n对象的地址。pa=&n是让pa指针指向类D的对象,这是正确的,并且也无二义性。请问baojian88888()兄台,你写的上面这句话我觉得有问题:既然pa是指向类A对象的指针,而D是A的派生类,也就是说D中的g()和d在A中是没有的,你这是让pa指向D的对象n,是不是要出错,我觉得把A,D交换一下如下:
    A n;
    D *pa;
    pa = &n;这样就对了,而像你写的那样是不是要出问题?我不太明白,请指教!!!!
      

  23.   

    多重继承是搭建复杂对象的优秀手段,但由于它制作复杂,很多编译器为使体系简单
    而不支持多重继承,这不是多重继承理论的错。是太多厂家的错。没有支持多重继承
    的编译器,也就谈不上是不是一个优秀oop语言的必须因素。
      

  24.   

    考官对,楼主错!首先应该搞清楚下列概念:
    基类
    虚函数
    纯虚函数
    单一继承
    多重继承我的观点:
    1、继承关系呈现线性,几乎是不可能的。
    假设A 是基类,B继承A,C也继承A。 这是什么结构,线性吗?
    但这是属于“单一继承”。2、虚拟基类就是抽象基类
    虚拟类 = 虚基类 = 虚拟基类 = 抽象类 = 抽象基类 3、 引进虚拟基类目的不只是为了解决二义性问题。
    并不是多重继承才用虚拟基类
    如果你说在单一继承中没必要用虚拟基类,那只是你设计的类太少, 还没有领悟。
      

  25.   

    改天研究
    csdn好久没有这种东西了
      

  26.   

    认真看看C++的语法书就行了,Primer说得蛮详细的
      

  27.   

    虚拟基类是含有虚拟函数的类(错)
    抽象基类是含有纯虚拟函数的类(对)我觉得首先要弄清楚“虚拟基类”和“抽象基类”的区别。
    “抽象基类”比较好懂,只要含有纯虚拟函数的类就是。
    “虚拟基类”的概念有些混淆.思过崖说法是对的。
    考官的问题没说清除或者他就是象我一样根本没有理解“虚拟基类”和“抽象基类”的区别
    还好,今天问了一个大虾大虾说:
    1)虚基类不是一种类型的类,它只是处于某种位置的类
    2)虚拟基类并不需要有虚函数,虽然常常都有
    3)任何派生关系,都可以在前面加个virtual,只是大多数情况下,这没有意义。
    4)虚拟基类甚至根本不需要有成员。
    5)只有出现菱形,而且菱形顶部的基类含有数据成员时,虚基类才有意义。
      

  28.   

    好像楼主和考官发生了误解,考官没有把问题描述清楚是主要的,我估计他指的是抽象类,(不过这种自大的考官是不会在意自己怎么描述问题的),而楼主所说的虚拟基类,大概说虚拟继承会更加明白一些。话说回来,c++里有很多东西容易混淆的。不过,按MSDN上说的:
    Virtual base classes offer a way to save space and avoid ambiguities in class hierarchies that use multiple inheritance. 
    楼主是没有错的
      

  29.   

    (思过崖)说的对
     TrueZq(xx) 讲的也很明白
    不要把虚函数和虚基类混在一起,他们没有什么必然的关系
    不要把虚函数和抽象类搞在一起,他们也没有什么必然的联系
      

  30.   

    回复人: thundenet(雷) ( 
    --------------------------
    请问baojian88888()兄台,你写的上面这句话我觉得有问题:既然pa是指向类A对象的指针,而D是A的派生类,也就是说D中的g()和d在A中是没有的,你这是让pa指向D的对象n,是不是要出错,我觉得把A,D交换一下如下:
    A n;
    D *pa;
    pa = &n;这样就对了,而像你写的那样是不是要出问题?
    --------------------------你需要再看看C++基础方面的书
    解释如下:#include <stdio.h>class A // 动物类
    {
    public:
      void f() // 吃东西
      {
        printf("A::f() eat!\n");
      }
    protected:
      int a;  // 嘴巴的大小
    };class B : virtual public A  // 会飞的动物
    {
    protected:
      int b;  // 翅膀的个数
    };class C : virtual public A  // 会跑的动物
    {
    protected:
      int c;  // 腿的个数
    };class D : public B, public C // 鹤
    {
    public:
      void g()  // 随便一个什么动作
      {
        f();    // 吃东西
      }
    private:
      int d;    // 鹤的品种(中国鹤还是美国鹤)
    };void main()
    {
      D n;        // 一只鹤
      A *pA;      // 一个动物的位置(只要是动物,你都可以坐在这)
      pA = &n;    // 让一只鹤坐在一个动物的位置上  B *pB;      // 一个位置,指向一个会飞的动物(只要会飞的动物,你就可以坐在这)
      pB = &n;    // 让一只鹤坐在一个会飞动物的位置上
    /*
      A a;        // 一个动物
      D *pD;      // 一个位置,指向一只鹤(只要是鹤,你就可以坐在这)
      pD = &a;    // 让一个动物坐在一只鹤的位置上 (这句话就不对了)*/
    }// 最后一句当然有问题了,因为 a 只是一个抽象的动物,你不知道是一只什么样的动物,
    // 怎么能随便让它坐在鹤的位置上呢?
      

  31.   

    ==================================
    考官对,楼主错!首先应该搞清楚下列概念:
    基类
    虚函数
    纯虚函数
    单一继承
    多重继承我的观点:
    1、继承关系呈现线性,几乎是不可能的。
    假设A 是基类,B继承A,C也继承A。 这是什么结构,线性吗?
    但这是属于“单一继承”。2、虚拟基类就是抽象基类
    虚拟类 = 虚基类 = 虚拟基类 = 抽象类 = 抽象基类 3、 引进虚拟基类目的不只是为了解决二义性问题。
    并不是多重继承才用虚拟基类
    如果你说在单一继承中没必要用虚拟基类,那只是你设计的类太少, 还没有领悟。
    =======================================TrueZq(xx) 理解完全错误。1。你指出的单一继承能说明什么呢?
    2。虚拟基类只有在虚拟继承后才叫虚拟基类。虚拟基类!=抽象基类
    3。引进虚拟基类目的当然就是解决二义性问题, 单一继承当然没必要用虚拟基类
      

  32.   

    to: xiaohyy(醉大饿极)  :
    能不能写代码例子2、虚拟基类就是抽象基类这对吗???
      

  33.   

    To yintongshun(踏雪有痕) :我也说个,用两个3两个8只用乘除如何得到24,人家说是小学三年级的
    答曰:根据我所知,这是某一年MS的面试题目,我自己想出了一个答案:8除以(3减去(8除以3))等于24,但没有时间写个C++程序验证这是不是唯一的答案。
    还有在此特别感谢今天下午给我发短消息让我去指定网址下载MSDN 2001的哪个兄弟!至于MSDN2003我已经有了,感觉MSDN2003跟VC++6似乎不协调,因为该版本里有关.NET FRAMEWORK的东西太多,对于VC++6而言,这些东西纯属“垃圾信息”,所以我优选选择没有“垃圾信息”的MSDN2001来陪伴VC++6。