前接Virtual机制,说到多重继承,虚函数是能完美兼容的,但是非虚函数不能,并且出现了有趣的过程,这里来探讨一下。
多重派生时,如果有非虚的重名同参数列表函数,就不能完美继承了,并且还可能产生隐性事故?
代码如下:
#include<iostream>
#include<stdio.h>
using namespace std;
class A{
public:
virtual ~A(){cout <<"A destruction"<<endl;}
virtual void func(){cout <<"A func."<<endl;}
};
class A2{
public:
virtual ~A2(){cout <<"A2 destruction"<<endl;}
virtual void func(){cout <<"A2 func."<<endl;}
void func2(){cout << "A2 func2."<<endl;}
};
class B{
public:
virtual ~B(){cout <<"B destruction"<<endl;}
virtual void func(){cout<<"B func."<<endl;}
};
class C:public A,public B
{
public:
virtual ~C(){cout <<"C destruction"<<endl;}
virtual void func(){cout <<"C func."<<endl;}
};
class D:public C
{
public:
virtual ~D(){cout<<"D destruction"<<endl;}
virtual void func(){cout<<"C func."<<endl;}
void func2(){cout << "D func2."<<endl;}
};
class E:public A2,public D
{
public:
virtual ~E(){cout <<"E destruction"<<endl;}
void fooE(){}
virtual void func(){cout <<"E func."<<endl;}
// void func2(){cout << "E func2."<<endl;}
};
int main(){
A a,a2;
A2 a2222;
B b,b2;
C c,c2;
D d,d2;
E e,e2;
// e.func2();
d.func2();
a2222.func2();
return 0;
}
注意,这个时候是能编译成功的!!!
但是如果把e.func2()的注释放开,就编译不成了。
# g++ non_virtual_multi.cpp
non_virtual_multi.cpp: In function ‘int main()’:
non_virtual_multi.cpp:50: 错误:对成员‘func2’的请求有歧义
non_virtual_multi.cpp:33: 错误:candidates are: void D::func2()
non_virtual_multi.cpp:14: 错误: void A2::func2()
这个感觉就有点像是从一个空指针里要数据,默认空指针是没事的,表达式也没事,但是如果当左值,要地址,就崩溃core dumped。(注意,是运行到那步才崩溃,我就见过奇葩笔试题,你不能说崩溃,你得把前边运行的打印也写出去,然后崩溃~~)
函数也是,我真要调用才发现问题(但还不是运行时,刚刚把调用语句写到代码里,编译就过不去了)
那么这点就很有趣了,同样是编译,写出调用和不写出调用两个样!
而同样是这种找不到要访问目标的错误,一种(访问函数)是编译错,另一种(去空地址取值)却是运行错。
关于函数入口地址:前边虚函数表的图画到了虚指针指向虚表,虚表存储函数入口,然后就能让对象找到相应的实现了,比如是这种?A::func() B::func
那么这些函数是怎么存放的,或者说怎么调用的?只记得有专门的数据段、代码段等,代码段肯定是加载到内存了(或是换出到虚拟内存了?)只要想要调用函数,就给出函数的地址,然后把代码段(可能换页调入内存)运行。
编译原理没怎么学,只能瞎推测一下:
这个多重继承,派生类总得给func2()找一个目标函数地址用于执行的,绑定谁却没个依据。但是编译成功了,所以不是在编译阶段就绑定的func2()。确切的说,不是类的声明定义阶段。
既然是出现调用语句才提示请求起义,似乎这种绑定也未发生在对象的声明与定义阶段。
抑或是,纯粹的编译器优化,编译器本来就解释不了这个歧义。不调用就认为没有风险,不提示歧义,有调用就提示歧义。这样解释更靠谱点,毕竟都是编译阶段嘛,感觉用阶段论有点站不住脚,至少我解释不太好。
不能对象自己调用歧义函数,那如果用类指针去调用呢?也不会编译错,运行也没问题,因为非虚函数,基类指针指向派生类对象,再去调用该函数,指的是找基类的func2(),这个没歧义,没法这么测。反过来测呢,派生类又不允许指向基类对象,没办法。
而关于访问空指针内容的崩溃错误。应该是取空地址是到那一步才会错,至于为什么不在编译阶段指针ptr赋值NULL时就跟踪它,发现有它再去调用func()之类的成员的代码就让它编译错,和这个多重继承一样处理呢?估计是不好跟踪,你又不能一开始就禁止赋值NULL,然后后续ptr变来变去,可能编译器办不到这个事,另外,这里边也有动态的,编译时就不知道的东西,比如你要在程序运行时手动输入一个值0,然后指针就赋值成这个值了。编译器无法未卜先知。而类的相关函数入口地址,却是应该在编译阶段就绑定的。
今天就先到这了,累,没思路。有空各种内存分布,代码、数据,高低地址,各种过程,再巩固一下。把内存分布这个地方的知识点再综合一下,总结一下。
相关问题比如代码段是先加载到内存,还是扔到硬盘缓存,运行的时候再换页?这个虽然可能是操作系统控制的,一视同仁,但是其实如果能优化,是有好处的,所以可能可以设置。