【问题标题】:Subclass casting, and pointer address changes子类转换和指针地址更改
【发布时间】:2021-04-08 22:42:49
【问题描述】:

我在 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 的地址。

【问题讨论】:

  • 我不相信你能得到 "root" 地址。不过,我会问一个单独的问题。根据标准的 AFAICT,甚至不能保证基类成员或子对象被放置在较低的内存地址中。换句话说,不能保证the_a 的地址低于the_b 或类似的地址。

标签: c++ casting


【解决方案1】:

是的,虚拟基础的一个简单实现是在派生对象的已知位置放置一个指向虚拟基础子对象的指针。

如果您有多个虚拟基地,那就有点贵了。更好的表示(我认为是微软的专利)使用自相对偏移。由于这些对于每个子对象都是不变的(也就是说,它们不依赖于对象地址),因此它们可以在静态内存中存储一​​次,您所需要的只是在每个子对象中指向它们的单个指针。

如果没有这样的数据结构,交叉转换将无法工作。即使 B 子对象不可见,您也可以从虚拟基础 A 交叉转换到虚拟基础 A 内的虚拟基础 B(如果我没记错的话,前提是只有两个基础具有共享的虚拟基础 X)。仔细想想,那是相当麻烦的导航:导航通过最派生类的形状描述符(可以看到 A 和 B)。

更令人毛骨悚然的是,如此表示的结构是“根据标准法则”在构造和销毁过程中动态变化的,这就是复杂对象的构造和销毁缓慢的原因。然而,一旦完成,方法调用,甚至交叉调用,都非常快。

【讨论】:

    【解决方案2】:

    是的,您可以认为这是安全的。保证 C++ 中的指针类型转换能够正确调整指针以说明从基数到派生或反之亦然的转换。

    也就是说,您必须小心不要将系统推得太远。例如,编译器不会正确地进行这种转换:

    ClassB* b = new ClassB;
    ClassC* c = (ClassC*)(void*)b;
    

    这会中断,因为从 C 到 A 的强制转换通过 void* 进行了汇集,因此有关指针在 B 对象内的位置的信息丢失了。

    直接强制转换不起作用的另一种情况是虚拟继承。如果你想从派生类转换为虚拟基类或反之亦然,我相信你必须使用 dynamic_cast 操作符来确保转换成功。

    【讨论】:

    • 感谢指正!这种情况很少出现,以至于我没有太多机会尝试它。
    • 关于虚拟继承的好电话,这是一个棘手的主题。我没有意识到你必须使用dynamic_cast,但你确实这样做了。请参阅 C++03 §5.2.9 第 5 和 8 段,其中明确要求在从基类转换为派生类时继承是非虚拟的。
    【解决方案3】:

    这是多重继承的完美标准用法,它适用于任何兼容的编译器。但是,您应该注意,在第一种情况下不需要显式转换,而在第二种情况下,“C 风格转换”可以替换为 static_cast

    是否可以强制子类位置的顺序。

    否:布局是实现定义的,您的代码不应依赖此顺序。

    【讨论】:

    • 我明白了。那么超类(例如A类)是否有可能获取整个结构的根地址(如果它本身是A类的地址,或者如果它是B类的一部分,则为B类的地址)?我正在构建一个依赖于根地址的内存池实现。
    • @kamziro:实际上,我认为没有,但我不得不说我很惊讶你甚至需要那个
    • 我明白了。我通过子类化实现的内存池实现需要将释放的地址返回到池中,而不必存储基地址节省了 4 个字节。四个微不足道的字节..我想如果没有选择那就太糟糕了:)
    • 实际上,您可以管理(而不是强制)非虚拟继承的排序,通过一次仅从一个类派生,您可以保证按照派生顺序获得线性排序。
    猜你喜欢
    • 2013-07-12
    • 1970-01-01
    • 2021-09-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多