c++ - 转换 Derived** → Base** 是错误的吗?还有什么选择?

标签 c++ casting containers multiple-inheritance vtable

上下文

我的目标是有一个包含和操作几个基类对象的基容器类,然后有一个包含和操作几个派生​​类对象的派生容器类。根据 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 相关 - 如果没有 virtual 方法,错误似乎不会发生)。你可以run it here , 和 see 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.

最佳答案

有很多因素使这段代码变得非常糟糕并导致了这个问题:

  1. 为什么我们首先要处理双星类型?如果std::vector不存在,为什么不自己写一个?

  2. 不要使用 C 风格的转换。您可以将指向完全不相关的类型的指针相互转换,并且编译器不允许阻止您(巧合的是,这正是这里发生的事情)。使用 static_cast/dynamic_cast相反。

  3. 假设我们有 std::vector , 为了便于记法。您正在尝试转换 std::vector<Derived*>std::vector<Base*> .这些是不相关的类型(对于 Derived**Base** 也是如此),将一个转换为另一个在任何方面都是不合法的。

  4. 从/到 derived 的指针转换不一定是微不足道的。如果你有 struct X : A, B {} , 然后是指向 B 的指针base 将不同于指向 A 的指针base (并且有一个 vtable 在起作用,也可能不同于指向 X 的指针)。它们必须是,因为(子)对象不能驻留在相同的内存地址。当您转换指针时,编译器将调整指针值。如果您(尝试)转换指针数组,这当然不会/不能发生在每个单独的指针上。

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

(除非满足 empty base optimization 的条件,但您不是这种情况)。

关于c++ - 转换 Derived** → Base** 是错误的吗?还有什么选择?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54632425/

相关文章:

c++ - jGRASP c++ 安装问题找不到-lfeglut

c++ - int 到 float 的转换会产生警告?

java - 当我转换包含变量的对象时,为什么它不显示变量的值?

java - 使用反射在 Java 中动态显式转换原始类型

python - 为什么容器的最大尺寸有符号位?

c++ - 这段代码中如何使用 map<string, set<string>>?

c++ - 构造函数在这一行中做什么?

ios - C# 规范是否允许从 ulong 转换为 int? Monotouch 给出一个 NumberOverflow

windows - 如何从 docker 容器中的文件到 Windows 容器中的主机

docker 使容器港口向公众开放