多重继承常常被认为是 OOP 中一种复杂且不必要的部分。多重继承面临 crash 的场景并非难以想象,来看下面的例子。

1. 名称冲突

来看以下情况:

如果 Dog 类以及 Bird 类都有一个名为 eat() 的方法,而子类又没有 override 该方法。如果此时调用子类的 eat() 方法,编译器就会报错,指出 eat() 的调用有歧义(不知道是调用从 Dog 类继承而来的 eat() 方法,还是从 Bird 类继承而来的 eat() 方法)。代码如下:

class Dog
{
public:
virtual void eat() {};
}; class Bird
{
public:
virtual void eat() {};
}; class DogBird : public Dog, public Bird
{ }; int main()
{
DogBird db;
db.eat(); // BUG! Ambiguous call to method eat()
return 0;
} /*
编译错误:
[purple@archlinux AAA]$ clang++ aa.cc
aa.cc:21:8: error: member 'eat' found in multiple base classes of different types
db.eat(); // BUG! Ambiguous call to method eat()
^
aa.cc:4:18: note: member found by ambiguous name lookup
virtual void eat() {};
^
aa.cc:10:18: note: member found by ambiguous name lookup
virtual void eat() {};
^
1 error generated.
*/

解决方法:

#include <iostream>
using namespace std; class Dog
{
public:
virtual void eat() {cout << "The Dog has eaten." << endl;};
}; class Bird
{
public:
virtual void eat() {cout << "The Bird has eaten." << endl;};
}; class DogBird : public Dog, public Bird
{ }; int main()
{
DogBird db;
static_cast<Dog>(db).eat(); // Slices, calling Dog::eat()
db.Bird::eat(); // Calls Bird::eat()
return 0;
} /*
Output:
The Dog has eaten.
The Bird has eaten.
*/

为了消除歧义,要么在 DogBird类重写 eat() 方法,要么显示的指明调用的是哪一个父类的版本。

2. 歧义基类

来看以下情况:

虽然可能产生 name ambiguity,但是 C++ 允许这种类型的类层次结构。例如,如果 Animal 类有一个 public 方法 sleep(),那么 DogBird 对象将无法调用这个方法,因为编译器不知道调用 Dog 继承的版本还是 Bird 继承的版本。代码如下:

class Animal
{
public:
void sleep(){}
}; class Dog : public Animal
{
}; class Bird : public Animal
{
}; class DogBird : public Dog, public Bird
{
}; int main()
{
DogBird db;
db.sleep();
return 0;
} /*
发生编译错误 [purple@archlinux ~]$ clang++ aa.cc
aa.cc:25:8: error: non-static member 'sleep' found in multiple base-class subobjects of type 'Animal':
class DogBird -> class Dog -> class Animal
class DogBird -> class Bird -> class Animal
db.sleep();
^
aa.cc:7:10: note: member found by ambiguous name lookup
void sleep(){}
^
1 error generated.
*/

使用“菱形”类层次结构的最佳方法是将最顶部的类设置为抽象类,所有方法都设置为纯虚方法。由于类只声明方法而不提供定义,在基类中没有方法可以调用,因此在这个层次上就不会产生歧义。代码如下:

#include <iostream>
using namespace std; class Animal
{
public:
virtual void sleep() = 0;
}; class Dog : public Animal
{
public:
virtual void sleep()
{
cout << "Dog sleep!" << endl;
}
}; class Bird : public Animal
{
public:
virtual void sleep()
{
cout << "Bird sleep!" << endl;
}
}; class DogBird : public Dog, public Bird
{
public:
// 注意:虽然从语法上来说,DogBird可以不override sleep方法
// 但是如此一来,再调用DogBird类的sleep方法时,会分不清是Dog类的还是Bird类的
virtual void sleep()
{
cout << "DogBird sleep!" << endl;
}
}; int main()
{
DogBird db;
db.sleep();
return 0;
} /*
Output:
DogBird sleep!
*/

小结

我们往往会在定义一个“既是一个事物同时又是另外一个事物”的情况下使用多重继承,然而,实际上遵循这个模式的实际对象很难恰如其分的转换为合适的代码,因此在工程中,我们要尽量避免使用多重继承。

Cpp多重继承会产生的问题的更多相关文章

  1. 不可或缺 Windows Native (22) - C++: 多重继承, 虚基类

    [源码下载] 不可或缺 Windows Native (22) - C++: 多重继承, 虚基类 作者:webabcd 介绍不可或缺 Windows Native 之 C++ 多重继承 虚基类 示例1 ...

  2. cl.exe命令方式编译cpp

    直接在命令行窗口调用cl编译cpp文件 往往不能通过. 主要原因是一些头文件及可执行文件未在环境变量中设置.可以通过执行VSVAR32.BAT批处理文件来设置环境变量,注意vs2005跟2008的放置 ...

  3. C++-多重继承的注意点

    1, 钻石型多重继承如果不想要底部的类有重复的变量,则需要声明为virtual继承 class File{...}; class InputFile: virtual public File{..}; ...

  4. C++中的多重继承与虚继承的问题

    1.C++支持多重继承,但是一般情况下,建议使用单一继承. 类D继承自B类和C类,而B类和C类都继承自类A,因此出现下图所示情况: A          A \          / B     C ...

  5. C++多重继承虚表的内存分布

    接前面虚表的内存分布,今天重点看多重继承的虚表内存分布,简单的说,继承几个类便有几个虚表,如下代码 class Drive : public Base1, public Base2, public B ...

  6. Linux Debugging(四): 使用GDB来理解C++ 对象的内存布局(多重继承,虚继承)

    前一段时间再次拜读<Inside the C++ Object Model> 深入探索C++对象模型,有了进一步的理解,因此我也写了四篇博文算是读书笔记: Program Transfor ...

  7. 【ThinkingInC++】75、多重继承

    第九章 多重继承 9.2 接口继承 Intertfacees.cpp /** * 书本:[ThinkingInC++] * 功能:接口继承Interfaces.cpp * 时间:2014年10月28日 ...

  8. 《C++ Primer Plus》14.3 多重继承 学习笔记

    多重继承(MI)描述的是有多个直接基类的类.与单继承一样,共有MI表示的也是is-a关系.例如,可以从Awiter类和Singer类派生出SingingWaiter类:class SingingWai ...

  9. C++解析(24):抽象类和接口、多重继承

    0.目录 1.抽象类和接口 1.1 抽象类 1.2 纯虚函数 1.3 接口 2.被遗弃的多重继承 2.1 C++中的多重继承 2.2 多重继承的问题一 2.3 多重继承的问题二 2.4 多重继承的问题 ...

随机推荐

  1. Oracle连乘聚合函数 MUL

    Oracle提供了求和(SUM),平均值(AVG)等聚合函数,但没有提供连乘的聚合函数. 比如有一个表如下: ID NUM 1 4 2 2 3 2 如果要求NUM列的连乘数,即求: 4*2*2 ,目前 ...

  2. ubuntu下xampp的安装

    转载:http://blog.csdn.net/zhoushengchao/article/details/6006584 首先,请从www.xampp.org下载最新版XAMPP.然后,将安装文件服 ...

  3. ios 唯一标示符

    大家知道苹果每部 iOS 设备都有一个 UDID,它就像设备的身份证一样,记录着设备的名称.类型甚至一些关于用户的私人信息.通常情况下,UDID 的一个最大功能就是帮助广告发布商向特定用户推送定向广告 ...

  4. cocos2dx中的场景和使用方法

    1.一个游戏中有且只有一个导演,但是至少有一个场景 2.场景是游戏元素节点数的根节点,也可以理解为该场景下的渲染树的根节点 3.场景是一个容器,包含了该场景下的所有游戏元素,比如层,精灵 4.场景是导 ...

  5. jQuery中ready与load事件

    jQuery中ready与load事件(来自慕课网) jQuery有3种针对文档加载的方法 $(document).ready(function() { // ...代码... }) //docume ...

  6. ASP.Net MVC利用NPOI导入导出Excel

    因近期项目遇到所以记录一下: 首先导出Excel: 首先引用NPOI包 http://pan.baidu.com/s/1i3Fosux (Action一定要用FileResult) /// <s ...

  7. 转载:JS快速获取图片宽高的方法

    快速获取图片的宽高其实是为了预先做好排版样式布局做准备,通过快速获取图片宽高的方法比onload方法要节省很多时间,甚至一分钟以上都有可能,并且这种方法适用主流浏览器包括IE低版本浏览器. 我们一步一 ...

  8. Netsharp快速入门(之5) 基础档案(之D 实体建模 生成实体代码、同步数据库、配置插件运行时)

    作者:秋时 杨昶   时间:2014-02-15  转载须说明出处 3.3.1  同步数据库并生成dll文件 1.在基础档案和销售管理项目上右击,选择同步数据库结构来创建数据库表 2. 在基础档案项目 ...

  9. Python 删除列表中的重复数据

    list0=['b','c', 'd','b','c','a','a'] 方法1:使用set() list1=sorted(set(list0),key=list0.index) # sorted o ...

  10. PHP字符串处理常用方法

    strlen("字符串");//取字符串的长度 strcmp($a,$b);  //判断两个字符串是否相同,相同返回0,$a>$b返回1,$a<$b返回-1,区分大小写 ...