c – 铸造Derived **→Base **错了吗?有什么选择?

上下文

我的目标是拥有一个包含和操作几个基类对象的基本容器类,然后是一个包含和操作多个派生类对象的派生容器类.根据this answer的建议,我试图通过让每个包含一个指针数组(一个Base **和一个Derived **),并在初始化基本容器类时从Derived **转换为Base **.

但是,我遇到了一个问题 – 尽管编译得很好,但在操作包含的对象时,我会有段错误或者会调用错误的方法.

问题

我把问题归结为以下最小的情况:

#include <iostream>

class Base1 {
public:
    virtual void doThing1() {std::cout << "Called Base1::doThing1" << std::endl;}
};

class Base2 {
public:
    virtual void doThing2() {std::cout << "Called Base2::doThing2" << std::endl;}
};

// Whether this inherits "virtual public" or just "public" makes no difference.
class Derived : virtual public Base1, virtual public Base2 {};

int main() {
    Derived derived;
    Derived* derivedPtrs[] = {&derived};
    ((Base2**) derivedPtrs)[0]->doThing2();
}

您可能希望打印“Called Base2 :: doThing2”,但……

$g++ -Wall -Werror main.cpp -o test && ./test
Called Base1::doThing1

确实 – 代码调用Base2 :: doThing2,但Base1 :: doThing1最终被调用.我也有这个带有更复杂类的段错误,所以我假设它是与地址相关的hijinks(可能与vtable相关 – 如果没有虚拟方法,似乎不会发生错误).你可以run it heresee the assembly it compiles to here.

You can see my actual structure here – 它更复杂,但它将它与上下文联系起来,并解释了为什么我需要这样的东西.

当Derived *→Base *执行时,为什么Derived **→Base **无法正常工作,更重要的是,将一组派生对象作为基础对象数组处理的正确方法是什么(或者,如果没有,制作一个可以包含多个派生对象的容器类的另一种方法)?

我无法在向上转换之前进行索引(((Base2 *)derivedPtrs [0]) – > doThing2()),我担心,因为在完整代码中该数组是一个类成员 – 我不确定它是什么一个好主意(甚至可能)在每个地方手动投射包含的对象在容器类中使用.如果这是解决这个问题的方法,请纠正我.

(我不认为这种情况有所不同,但我在一个std :: vector不可用的环境中.)

编辑:解决方案

许多答案表明,单独转换每个指针是拥有一个可以包含派生对象的数组的唯一方法 – 事实上确实如此.但是,对于我的特定用例,我设法使用模板解决了问题!通过为容器类应包含的内容提供类型参数,而不是必须包含派生对象的数组,可以在编译时将数组的类型设置为派生类型(例如,BaseContainer< Derived> container(length) ,arrayOfDerivedPtrs);).

Here’s a version of the broken “actual structure” code above, fixed with templates.

解决方法:

有很多事情使得这段代码非常糟糕并且导致了这个问题:

>为什么我们首先要处理两星级?如果std :: vector不存在,为什么不编写自己的?
>不要使用C风格的演员阵容.您可以将指向完全不相关的类型的指针互相转换,并且不允许编译器阻止您(巧合的是,这正是这里发生的事情).请改用static_cast / dynamic_cast.
>假设我们有std :: vector,为了便于表示法.您正在尝试强制转换std :: vector< Derived *>到std :: vector< Base *>.那些是不相关的类型(对于Derived **和Base **也是如此),并且将一个转换为另一个在任何方面都是不合法的.
>从/派生的指针强制转换不一定是微不足道的.如果你有结构X:A,B {},那么指向B base的指针将不同于指向A base的指针†(并且在游戏中有vtable,也可能与指向X的指针不同).它们必须是,因为(子)对象不能驻留在同一个内存地址.在转换指针时,编译器将调整指针值.如果您(尝试)强制转换指针数组,这当然不会/不会发生.

如果你有一个指向Derived的指针数组并希望获得指向Base的指针数组,那么你必须手动转换每个指针.由于两个数组中的指针值通常都不同,因此无法“重用”同一个数组.

†(除非满足empty base optimization的条件,否则不适用).

上一篇:C:访问shared_ptr的容器应该返回raw或shared ptr吗?


下一篇:烂泥:学习ubuntu远程桌面(一):配置远程桌面