我已经在c ++中使用了多次继承很长一段时间了,但是今天才意识到这可能意味着指针地址在引用它们作为子类之一时会有所不同。
例如,如果我有:
class ClassA{
public:
int x;
int y;
ClassA(){
cout << "ClassA : " << (unsigned int)this << endl;
}
};
class ClassC{
public:
int cc;
int xx;
ClassC(){
cout << "ClassC : " << (unsigned int)this << endl;
}
};
class ClassB : public ClassC, public ClassA{
public:
int z;
int v;
ClassB(){
cout << "ClassB : " << (unsigned int)this << endl;
}
};
int main(){
ClassB * b = new ClassB();
}
在构造函数上打印时,类A和类C具有不同的地址。
然而,当我试图将它们互相投放时,它只是自动运行:
ClassA * the_a = (ClassA*)b;
cout << "The A, casted : " << (unsigned int)the_a << endl;
ClassB * the_b = (ClassB*)the_a;
cout << "The B, casted back : " << (unsigned int)the_b << endl;
我想这些信息可以由编译器从代码中派生出来,但可以安全地假设这适用于所有编译器吗?
附加问题:是否可以强制子类位置的顺序?例如,如果我需要首先定位classA(本质上,共享相同的指针位置)和ClassC它的子类,我只需要将它放在子类的声明中吗? 更新好的,看起来无法强行执行订单。是否仍然可以找到结构的“根”地址,分配给子类的地址的开始,在超类级别?例如,从ClassA获取classB的地址。
答案 0 :(得分:7)
这是多重继承的完美标准用法,它适用于任何兼容的编译器。但是,您应该注意在第一种情况下显式转换为不必要的,并且在第二种情况下可以用static_cast
替换“C样式转换”。
是否可以强制子类位置的顺序。
否:布局是实现定义的,您的代码不应该依赖于此顺序。
答案 1 :(得分:3)
是的,你可以认为这是安全的。 C ++中的指针类型转换可以保证正确地将指针调整为基本到派生或反之的转换。
那就是说,你必须小心不要把系统推得太远。例如,编译器无法正确进行此转换:
ClassB* b = new ClassB;
ClassC* c = (ClassC*)(void*)b;
这会中断,因为从C到A的转换通过void *进行漏斗,因此有关指针在B对象内的位置的信息将丢失。
直接强制转换不起作用的另一种情况是虚拟继承。如果你想从派生类转换为虚拟基类,反之亦然,我相信你必须使用dynamic_cast运算符来确保转换成功。
答案 2 :(得分:1)
是的,虚拟基础的一个简单实现是在派生对象中的已知位置放置一个指向虚拟基础子对象的指针。
如果你有多个虚拟基地有点贵。更好的表示(我认为微软的专利)使用自相关偏移。由于它们对于每个子对象都是不变的(也就是说,它们不依赖于对象地址),因此它们可以在静态存储器中存储一次,并且您需要的只是在每个子对象中指向它们的单个指针。
如果没有这样的数据结构,交叉转换将无法正常工作。即使B子对象不可见,也可以从虚拟基础A内部虚拟基础A交叉转换为虚拟基础B(如果我没记错,只提供两个基础具有共享虚拟基础X)。当你想到它时,这是非常多毛的导航:导航通过最派生类的形状描述符(可以看到A和B)。
更为毛茸茸的是,“标准法则”所要求的结构在施工和破坏过程中需要动态变化,这就是复杂物体的建造和破坏速度缓慢的原因。然而,一旦它们被制作出来,方法调用,甚至是交叉调用,都非常快。