有关虚函数的一点想法
上行转换的理解
此处我的理解类似于利用基类的语法或者语义重新解释cast后的指针,这就是为什么子类中成员的顺序是从基类到子类,如果是先子类再父类的内存存储,会造成裁剪赋值中的解析错误(其实我觉得准确的说,应该是所赋值的区域不对称),导致将子类的信息变更其存储位置,放到了基类访问地方,然后因为重新解释的问题,导致调用对应函数或者成员变量产生不可预估的结果。
虚函数会让类或者类对象增加什么
#include <iostream>
using namespace std;
class BTEST {
public:
BTEST():b(10){}
private:
int b;
};
class CTEST {
public:
CTEST():c(10){}
virtual void testC() {
cout << "C" << endl;
}
private:
int c;
};
int main()
{
return 0;
}
单继承中虚表中的虚函数顺序是什么
先父类再子类
因为主要还是想转换的时候,保证转换之后的类依然具有其本身的类结构,试想如果子类排在父类的虚函数前面,那么进行上行转换后得到的虚表就不是其基类的虚函数,导致调用类的虚函数发生改变,可能就不是自己的虚函数了,这时一旦虚函数内部涉及操作对应的this指针,结果可能不可能预估。
为什么dynamic_cast可以检查进行下行转换,并给出是否能转换的返回值
主要还是因为虚函数表的第一个slot(槽)中含有type_info信息,可以理解为我对我原生的类型进行了记录,那么在进行转换的时候自然可以通过比对类型信息,来判断是否可以合理的转换.
static_cast和dynamic_cast在某种程度上是一致的
最主要区别还是static_cast是不会做RTTI(运行时类型识别)的,导致转换后的结果可能有异常
例如
# include <iostream>
using namespace std;
class A{
public:
virtual void testA(){
cout << "A" << endl;
}
};
class B:public A{
public:
virtual void testA(){
cout << "B" << endl;
}
};
int main()
{
A *a = new A();
B *b = static_cast<B*> (a);
b->testA();
delete a;
}
此处的转换依然是成功的,但是打印的结果依然是A的结果,他不存在类型识别。能转成功,但是调用会产生不可预估的错误。
因为static_cast本身就是属于一种c里面类型不安全的转换,相当于对这个指针进行转换,对指针指向的内存进行重新解析(个人理解)。
理想的情况下是应该尽量避免存在强制转换。如果不能,则应该尽量避免使用static_cast进行基类和子类之间的转换,static_cast可以很完美的作用于B->A->B这种类型,但是单纯的从A->B请使用dynamic_cast,其会根据type_info信息进行RTTI,保证我们的b->testA()的输出是我们需要的结果。
多重继承(含有虚函数的基类)为什么需要多个虚表
目前编译器对于多重继承的内存布局实现
例入
#include <iostream>
using namespace std;
class A{
public:
virtual void testA(){
cout << " A" << endl;
}
};
class B{
public:
virtual void testB(){
cout << " B" << endl;
}
};
class C:public A, public B{
public:
void testA(){
cout << "C::A" << endl;
}
void testB(){
cout << "C::B" << endl;
}
};
int main()
{
C *c = new C();
B* b = static_cast<B*>(c);
delete c;
return 0;
}
从结果可以看出多重继承的虚表指针的个数与含有虚函数的基类个数有关,与不含虚函数的基类个数无关与含有虚函数的基类个数有关,与不含虚函数的基类个数无关。
只用一个虚表的问题
主要还是因为进行上行转换的时候,会存在裁剪,如果多个虚表放在一起,就会存在每个虚函数存放在哪个slot的歧义性问题
这个可以复用上一个案例。
int main()
{
C *c = new C();
B* b = static_cast<B*>(c);//可以关注这一行
delete c;
return 0;
}
如果是一个虚表的情况下,上行转换(本质上其实还是赋值),那么会把C的虚表地址赋值给B的对象,这会造成两个问题。
first:导致B的虚函数表混乱,其虚表的前半段其实不属于自己,导致在进行函数调用的时候会发生错误(在精心设计的两个类有相同的成员和成员函数的情况下,这个应该是可以不报错的,此处并未进行测试)。
second:解决这个的办法我目前想到了一个,就是让每个子类维护一个数组,数组里面存储的基类虚函数在子类中的位置,这是是我自己瞎想的,或许可行,但是性能99%不行。
在上行转换中,static_cast和dynamic_cast其实一致
#include <iostream>
using namespace std;
class A {
public:
virtual void testA() {
cout << " A" << endl;
}
virtual void testAB() {
cout << "A::AB" << endl;
}
};
class B {
public:
virtual void testB() {
cout << " B" << endl;
}
virtual void testAB() {
cout << "B::AB" << endl;
}
};
class C :public A, public B {
public:
void testA() {
cout << "C::A" << endl;
}
void testB() {
cout << "C::B" << endl;
}
};
int main()
{
C* c = new C();
B* b = static_cast<B*>(c);
b->testAB();
B* b1 = (B*)(c);
b1->testAB();
B* b2 = dynamic_cast<B*> (c);
if (b2 != NULL) {
cout << " dynamic_cast success" << ", ";
b2->testAB();
}
delete c;
return 0;
}
是否可以从带有虚函数的类对象给没有虚函数的类进行赋值
我觉得是不行的,可以利用代码进行测试。
#include <iostream>
using namespace std;
class A {
public:
A():a(10){}
void testA() {
cout << "A:" << a << endl;
}
private:
int a;
};
class B :public A{
public:
B():b(20){}
virtual void testB() {
cout << "B:" << b <<endl;
}
private:
int b;
};
int main() {
B* b = new B();
A* a = static_cast<A*>(b);
a->testA();
delete b;
return 0;
}
可见,结果是可行,但是此处有点颠覆我的认真,因为此处对应的指针还是有一个虚表指针的。