C ++多态类是否可能没有任何功能?

时间:2012-08-18 11:18:54

标签: c++ polymorphism

我正在尝试创建一些只包含数据成员(没有函数)的类但我希望它们是多态的 - 我的意思是我将通过指向基类的指针传递对象,我需要能够dynamic_cast将它们转换为特定的派生类型(如果实例不是给定类型,则结果值为NULL。)

举个例子,我有一个项目:

struct Item {
    int x, y;
}

我还有一个移动的项目,另一个包含文本的项目:

struct MovingItem: virtual public Item {
    int speedX, speedY;
}

struct TextItem: virtual public Item {
    std::string text;
}

据推测,我必须使用上面的虚拟继承,因为我还想要一个移动并有文本的项目,但我只想要顶级Item中的一组坐标:

struct MovingTextItem: virtual public MovingItem, virtual public TextItem {
}

这些都可以正确定义,但是当我尝试dynamic_cast Item *来查看它是什么类型时,我的编译器会抱怨源类型不是多态的。

void example(Item *i) {
    MovingTextItem *mti = dynamic_cast<MovingTextItem *>(i);  // error!
}

如果我使用虚函数而不是数据成员重新实现整个事情,这将有效,但这似乎是浪费,因为我永远不需要覆盖任何东西。

我能想到的唯一解决方法是将type成员添加到基础Item类,并检查该成员而不是使用dynamic_cast,如果它的类型正确则请改用static_cast。 (缺点是我必须知道某处的所有对象类型,因此分配的type值不会发生冲突。)

这是最好的解决方案,还是有另一种方式?

澄清

为了论证,想象一下我将每个对象类型写入文件。 MovingItem转到一个文件,TextItem转到另一个文件,MovingTextItem转到这两个文件。因此,有一个实现每个接口的基类将无法工作,除非我能以某种方式告诉使用哪些接口,以便将它们写入正确的文件。

4 个答案:

答案 0 :(得分:6)

如果添加虚函数,则类将获得vtable,以便dynamic_cast起作用。一个无操作的虚拟析构函数就可以了。 (正如托尔斯滕所指出的那样,在你的情况下甚至可能需要非POD成员。)

但是,根据我自己的经验(我多年来一直反对这一点!)我会强烈建议在这种特殊情况下继承。请改用聚合。 Prefer composition over inheritance?您可能需要创建几个转发器,但这样获得的灵活性得到了回报(就以后能够在不影响整个系统的情况下更改实现而言)。

考虑 速度的项目,此外,文本是一个位置,而不是 < / em>一个以某种方式具有速度和文本的项目,并通过一些魔法继承该位置。如果您以后想要一个速度快但没有已知位置的物品会怎样?

要在聚合方案中实现结果,请定义构成有关特定功能的合同的“接口”。也不需要虚拟继承:“接口”只是具有纯虚函数的类。另请参阅:What does it mean to "program to an interface"?(如果您需要可维护的软件,则另一条重要规则。)

实施例

您可以定义四个“接口”(仅具有纯虚函数的类):PositionSpeedTextItemItem继承自前三个,并且不定义任何函数本身。您为前三个接口提供“参考实现”。 “参考impl。” for Item有三个数据成员(前三个接口的ref.inpl。)并转发到这些实现。现在您可以使用例如Position只要你需要有位置的东西,就不用知道究竟是什么。 dynamic_cast也有效。

您还可以定义没有虚拟方法的接口ItemBase,并从此接口继承PositionSpeedText。这将允许您通过相同的基本类型访问所有功能,并动态测试子接口的可用性。我认为仍然不需要virtual继承......

答案 1 :(得分:2)

您甚至可以在基类中添加虚拟析构函数。然后可以使用dynamic_cast。您还可以查看访问者模式。如果您有一个很小且很少更改的数据类型集并且对这些数据类型有更丰富的操作集,那么该模式可能非常有用。

答案 2 :(得分:1)

假设您可以这样做。你有MovingItem的速度。你有TextItem字符串。而且你甚至可能拥有MovingTextItem字符串和速度,它使用来自MovingItemTextItem的多个虚拟继承。

你把所有这些都放在std::vector<Item*>中。好的,很好。

你是如何使用它的?

每个需要Item*代码是否需要使用dynamic_cast来实现它所需的实际类型(如果它不是正确的类型则退出)?或者这些函数的调用者是否执行dynamic_cast?无论哪种方式,dynamic_cast很多只是为了使用这些值。

但那是次要的。真正的问题是:你如何删除他们?

请参阅,除非Itemvirtual 析构函数,否则在任何delete上调用Item*都会非常糟糕。没有虚拟析构函数,C ++无法调用任何派生的类的析构函数。所以你会以某种方式获得任何Item*项的实际类型。

这将需要大量dynamic_cast次操作。每次添加新的派生Item类时,您都需要在列表中添加另一个dynamic_cast项检查。

或者你可以给Item一个虚拟析构函数,让C ++完成它的工作。这样,您不需要一系列dynamic_cast来查找要删除它的实际对象类型。更好的是,因为它有一个虚拟析构函数,Item将是一个虚拟类型,C ++将允许你执行你似乎想要做的所有dynamic_cast

当然,您使用dynamic_cast这一事实是一个直接的危险信号,您的设计迫切需要修改。

答案 3 :(得分:0)

为了记录,我最终重新设计了这个,以便有一个Item类型,带有标志以指示哪些字段有效:

enum ItemType {HasSpeed = 1, HasText = 2};
struct Item {
    int type;
    int x, y;

    int speedX, speedY;
    std::string text;
}

如果速度和文本字段都有效,我只需设置type = HasSpeed | HasText。也许有点老派,但是很多更简单!