C++中virtual关键字解决了什么问题(四)

一、菱形继承
在众多编程语言中,支持继承这一特性的语言不在少数,但鲜有支持多继承的语言,因为其容易出现继承向上的二义性问题,比如臭名昭著的“菱形继承”。这里借用著名武侠小说《笑傲江湖》来举个例子说明这个问题。
我们知道,华山派有气宗和剑宗之分,其代表人物分别是岳不群和风清扬。岳不群膝下弟子令狐冲随其修炼气宗功夫,后又因种种机缘巧合受风清扬指点习得剑宗武功,算是气剑双修吧。以此故事为背景,所以有了下面的代码:

//华山派
class Huashan
{
public:
    Huashan(){}
    ~Huashan(){}
    void show(){cout << "华山派" << endl;}//自报家门
};
//华山派气宗
class Qizong : public Huashan
{
public:
    Qizong(){}
    ~Qizong(){}
    void best_skill(){cout << "《紫霞神功》" << endl;}//气宗宝典
};
//华山派剑宗
class Jianzong : public Huashan
{
public:
    Jianzong(){}
    ~Jianzong(){}
    void best_skill(){cout << "《独孤九剑》" << endl;}//剑宗绝技
};
//气剑双修派
class QiJian : public Qizong, public Jianzong
{
public:
    QiJian(){}
    ~QiJian(){}
};

int main(int argc, char* argv[])
{
    QiJian lhc;//气剑双修的令狐冲
    lhc.show();//令狐冲自报家门
    lhc.best_skill();//令狐冲使出最强武功
    return 0;
}

几个门派之间的关系正如这四个类描述的这样,简单明了。结果发现这段代码编译不过,令狐冲就此扑街。

make
gcc main.cpp -Wall -Werror -m64 -g -c -o main.o
main.cpp: In function ‘int main(int, char**)’:
main.cpp:243:9: error: request for member ‘show’ is ambiguous
     lhc.show();
         ^~~~
main.cpp:218:10: note: candidates are: void Huashan::show()
     void show(){cout << "华山派" << endl;}
          ^~~~
main.cpp:218:10: note:                 void Huashan::show()
main.cpp:244:9: error: request for member ‘best_skill’ is ambiguous
     lhc.best_skill();
         ^~~~~~~~~~
main.cpp:232:10: note: candidates are: void Jianzong::best_skill()
     void best_skill(){cout << "《独孤九剑》" << endl;}
          ^~~~~~~~~~
main.cpp:225:10: note:                 void Qizong::best_skill()
     void best_skill(){cout << "《紫霞神功》" << endl;}
          ^~~~~~~~~~
Makefile:7: recipe for target 'main.o' failed
make: *** [main.o] Error 1

看看编译报错,很容易就能脑补以下对话场面:
冲:“我是华山派弟子令狐冲!”
众喽啰:“哪个华山派?气宗还是剑宗?”
冲:“我要用我最强武功打败你们!”
众喽啰:“你要用气宗武功还是剑宗武功?”
众喽啰:“不说清楚不跟你比武!”
说白了就是编译器就像喽啰一样,不知道lhc对象调用的方法来自哪个类。
二、虚继承
为了解决“菱形继承”引起的二义性问题,C++采用虚继承的方式,使得在派生类中只保留一份间接基类的成员,避免了二义性和数据冗余问题。虚继承的实现需要在原有继承方式中增加virtual关键字。修改后的代码如下:

//华山派
class Huashan
{
public:
    Huashan(){}
    ~Huashan(){}
    void show(){cout << "华山派" << endl;}//自报家门
};
//华山派气宗
class Qizong : virtual public Huashan
{
public:
    Qizong(){}
    ~Qizong(){}
    void best_skill(){cout << "《紫霞神功》" << endl;}//气宗宝典
};
//华山派剑宗
class Jianzong : public virtual Huashan //添加在类名前即可
{
public:
    Jianzong(){}
    ~Jianzong(){}
    void best_skill(){cout << "《独孤九剑》" << endl;}//剑宗绝技
};
//气剑双修派
class QiJian : public Qizong, public Jianzong
{
public:
    QiJian(){}
    ~QiJian(){}
};

int main(int argc, char* argv[])
{
    QiJian lhc;//气剑双修的令狐冲
    lhc.show();//令狐冲自报家门
    lhc.Jianzong::best_skill();//令狐冲使出剑宗最强武功(多个父类都有的同名方法需要指明父类)
    /*紫霞神功没学,暂时不使出来了,当然,加上类名也是可以调用的*/
    return 0;
}

这样一来,令狐冲就坚定的认为只有一个华山派,不分气宗剑宗,体现出了他胸怀宽广和大侠风范,尔后使出了剑宗前辈风清扬传授他的独孤九剑,征服众喽啰(程序编译通过)。
上面代码执行结果如下:
./笑傲江湖
华山派
《独孤九剑》
三、思维扩展
C++之后的语言没有实现多继承还有哪些原因呢?我想大概有一种考虑是因为同时继承一个自己开发的类和一个别人提供的类而且这两个类恰好又继承自同一个类的概率实在太小了吧,毕竟令狐冲这样的大侠也就仅此一个哈。欢迎评论区留言说说你的看法。
(完)

posted on 2022-03-04 21:45  OrangeGLC  阅读(87)  评论(0)    收藏  举报

导航