【发布时间】:2019-02-11 14:11:43
【问题描述】:
上下文
我的目标是拥有一个包含和操作多个基类对象的基容器类,然后是一个包含和操作多个派生类对象的派生容器类。在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最终被调用。我在更复杂的类中也遇到了这个段错误,所以我认为它是与地址相关的 hijink(可能与 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 不可用的环境中。)
编辑:解决方案
许多答案表明,单独强制转换每个指针是拥有一个可以包含派生对象的数组的唯一方法——而且似乎确实如此。不过,对于我的特定用例,我设法使用 templates 解决了这个问题!通过为容器类应该包含的内容提供类型参数,而不是必须包含派生对象的数组,可以在编译时将数组的类型设置为派生类型(例如BaseContainer<Derived> container(length, arrayOfDerivedPtrs);)。
Here's a version of the broken "actual structure" code above, fixed with templates.
【问题讨论】:
-
虚拟基地真的很重要。
-
你为什么要选角?举个例子,因为
doThing1()和doThing2()都是虚拟的,所以不需要强制转换。这就是虚成员函数的全部意义所在。 -
@obskyr 在这种情况下,您没有正确地进行继承,并且您的示例也没有足够详细地说明您的问题。按照公式,您的问题的答案是“不要施放”。如果您尝试创建自己的
std::variant,则标记联合可能是更有用的解决方案。 -
@obskyr:不,因为这听起来根本不像是一个有用的继承模型。 “Base1 的容器”和“Base1”之间没有 is-a 关系,因此对这种关系建模没有意义。
-
我不同意; “一组 X”不是“一个 X”;您碰巧对它们都应用了一些通用操作这一事实并没有改变这一事实。您也可以设置一个重载集
foo(InputChannel&)和foo(InputChannelSet&),这将允许您完成相同的事情,而不会在InputChannel和InputChannelSet.
标签: c++ casting containers multiple-inheritance vtable