5

我正在尝试创建一些仅包含数据成员(无函数)的类,但我希望它们是多态的 - 我的意思是我将通过指向基类的指针传递对象,并且我需要能够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_castItem *什么类型时,我的编译器抱怨源类型不是多态的。

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

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

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

这是最好的解决方案,还是有其他方法?

澄清

为了参数,假设我正在将每个对象类型写入文件。 MovingItem转到一个文件,TextItem转到另一个文件,然后MovingTextItem转到两个文件。所以拥有一个实现每个接口的基类是行不通的,除非我能以某种方式告诉哪些接口正在使用,以便将它们写入正确的文件。

4

4 回答 4

6

如果您添加一个虚函数,这些类将获得一个 vtable 以便dynamic_cast工作。一个无操作的虚拟析构函数就可以了。(正如 Torsten 指出的那样,这在您的情况下甚至可能是必要的,因为您有非 POD 成员。)

然而,根据我自己的经验(多年来我一直在抵制这种做法!)我强烈建议在这种特殊情况下不要继承。请改用聚合。更喜欢组合而不是继承?您可能必须创建几个转发器,但由此获得的灵活性是值得的(就能够在不影响整个系统的情况下稍后更改实现而言)。

考虑一个具有速度项目,此外还有一个文本一个位置,而不是一个以某种方式具有速度和文本并通过某种魔法继承位置的项目。如果您以后想要一个有速度但位置未知的项目怎么办?

要在聚合方案中实现您的结果,请定义构成与特定功能相关的合同的“接口”。也不需要虚拟继承:“接口”只是具有纯虚函数的类。另请参阅:“编程到接口”是什么意思?(如果你想要可维护的软件,另一个重要的规则。)

例子

您可以定义四个“接口”(仅具有纯虚函数的类):PositionSpeed和. 从前三个继承而来,并且本身没有定义任何函数。您为前三个接口提供“参考实现”。“参考 impl。” for具有三个数据成员(前三个接口的 ref. impl.)并转发到这些实现。现在,您可以在任何需要有位置的东西的地方使用,而不必知道它到底是什么。也有效。TextItemItemItemPositiondynamic_cast

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

于 2012-08-18T11:22:53.660 回答
2

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

于 2012-08-18T11:25:48.243 回答
1

假设你可以做到这一点。你有MovingItem一个速度。你有TextItem一个字符串。你甚至可能会拥有一个字符串和速度,它使用来自andMovingTextItem的多个虚拟继承。MovingItemTextItem

你把所有这些都放在一个std::vector<Item*>. 好的。

你怎么用它?

是否每段代码都Item*需要使用dynamic_cast它需要的实际类型(如果它不是正确的类型则退出)?或者这些函数的调用者会做dynamic_cast吗?无论哪种方式,仅使用这些值就需要很多dynamic_cast

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

看,除非Item有一个virtual 析构函数,否则调用deleteanyItem*将是非常糟糕的。如果没有虚拟析构函数,C++ 就无法调用任何派生类的析构函数。因此,您需要以某种方式获取任何项目的实际类型。Item*

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

或者你可以只给出Item一个虚拟析构函数,让 C++ 完成它的工作。这样,您不需要一系列dynamic_casts 就可以找到实际的对象类型来删除它。更好的是,因为它有一个虚拟析构函数,Item它将是一个虚拟类型,C++ 会让你做所有dynamic_cast你想做的事情。

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

于 2012-08-19T04:02:14.353 回答
0

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

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

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

然后我只设置type = HasSpeed | HasText速度和文本字段是否有效。也许有点老派,但它简单!

于 2012-08-23T01:52:13.873 回答