我已经阅读了几个关于C ++动态转换的线程,所有人都声称它表明设计不好。在其他语言中,我在检查对象的类型时从未考虑过太多。我从不使用它作为多态性的替代,只有当强耦合看起来完全可以接受时。我经常遇到的这些情况之一:有一个列表(我在C ++中使用std :: vector)的对象,都是从一个公共基类派生的。该列表由一个允许知道不同子类的对象管理(通常它是管理对象类中私有类的一个小层次)。通过将它们保存在单个列表(数组,向量,...)中,我仍然可以从多态中受益,但是当一个操作意图作用于特定子类的对象时,我使用动态强制转换或类似的东西。
对于这种类型的问题,如果没有动态强制转换或我缺少的类型检查,是否有不同的方法?我真的好奇那些不惜一切代价避免这些的程序员会如何处理它们。
如果我的描述过于抽象,我可以在C ++中编写一个简单的例子(编辑:见下文)。
class EntityContacts {
private:
class EntityContact {
private:
virtual void someVirtualFunction() { }; // Only there to make dynamic_cast work
public:
b2Contact* m_contactData;
};
class InternalEntityContact : public EntityContact {
public:
InternalEntityContact(b2Fixture* fixture1, b2Fixture* fixture2){
m_internalFixture1 = fixture1;
m_internalFixture2 = fixture2;
};
b2Fixture* m_internalFixture1;
b2Fixture* m_internalFixture2;
};
class ExternalEntityContact : public EntityContact {
public:
ExternalEntityContact(b2Fixture* internalFixture, b2Fixture* externalFixture){
m_internalFixture = internalFixture;
m_externalFixture = externalFixture;
};
b2Fixture* m_internalFixture;
b2Fixture* m_externalFixture;
};
PhysicsEntity* m_entity;
std::vector<EntityContact*> m_contacts;
public:
EntityContacts(PhysicsEntity* entity)
{
m_entity = entity;
}
void addContact(b2Contact* contactData)
{
// Create object for internal or external contact
EntityContact* newContact;
if (m_entity->isExternalContact(contactData)) {
b2Fixture* iFixture;
b2Fixture* eFixture;
m_entity->getContactInExFixtures(contactData, iFixture, eFixture);
newContact = new ExternalEntityContact(iFixture, eFixture);
}
else
newContact = new InternalEntityContact(contactData->GetFixtureA(), contactData->GetFixtureB());
// Add object to vector
m_contacts.push_back(newContact);
};
int getExternalEntityContactCount(PhysicsEntity* entity)
{
// Return number of external contacts with the entity
int result = 0;
for (int i = 0; i < m_contacts.size(); ++i) {
ExternalEntityContact* externalContact = dynamic_cast<ExternalEntityContact*>(m_contacts[i]);
if (externalContact != NULL && getFixtureEntity(externalContact->m_externalFixture) == entity)
result++;
}
return result;
}
};
这是我在使用box2d物理的游戏中用于碰撞检测的类的简化版本。我希望box2d的细节不会分散我想要展示的内容。我有一个非常相似的类'Event',它创建不同类型的事件处理程序,它们以相同的方式构造(使用基类EventHandler的子类而不是EntityContact)。
答案 0 :(得分:8)
至少从我的角度来看,dynamic_cast
存在是有原因的,有时候使用它是合理的。这可能是其中一次。
根据您描述的情况,一个可能的替代方案可能是在基类中定义您需要的更多操作,但是将它们定义为(可能是静默地)失败如果为基类或其他不支持这些操作的类调用它们。
真正的问题是以这种方式定义您的操作是否有意义。回到典型的基于动物的层次结构,如果你正在与鸟类合作,那么Bird类定义一个fly
成员通常是明智的,对于少数无法飞行的鸟类来说,只是让它失败了(理论上应该将其重命名为attempt_to_fly
,但这很少能实现。)
如果你看到了很多这样的内容,那么它往往表明你的课程缺乏抽象 - 例如,你可能真的想要一个fly
或attempt_to_fly
而不是travel
或{{1}}。 {1}}成员,由个体动物决定是否通过游泳,爬行,行走,飞行等来做到这一点。
答案 1 :(得分:0)
此
但是当一个操作意图作用于特定的对象时 子类我使用动态转换或类似的东西
听起来像对象建模不正确。你有一个包含子类实例的列表,但它们实际上并不是子类,因为你不能以同样的方式对它们进行操作(Liskov等)。
一种可能的解决方案是扩展基类,使得您有一组某些子类可以覆盖的无操作方法。但这听起来仍然不太正确。
答案 2 :(得分:0)
问题非常广泛,因此需要考虑一些一般性问题:
dynamic_cast
会产生一定的成本。如果是关于运行时性能,请根据备选方案的成本进行检查。答案 3 :(得分:0)
通常在这种情况下,您不会管理派生对象列表,而是管理引用实际派生对象的接口指针(或基本指针)列表。只要您想对特定对象执行操作,就可以通过其接口/基类访问它,该接口/基类应该公开所有必需的功能。派生类应该通过特定的实现覆盖公开的基本功能。
答案 4 :(得分:0)
使用dynamic_cast是想要实现像Visitor或Command这样的设计模式的症状,所以我建议重构以使其显而易见
答案 5 :(得分:0)
与任何被认为是错误编程的方法一样,总会有一些例外。当一个程序员说“这样的事情是邪恶的而你永远不应该这样做”时,他们的确意味着“几乎没有理由使用这样的等等,所以你最好能够自己解释一下。”我,我自己,从来没有遇到dynamic_cast
是绝对必要且无法轻易重构的情况。但是,如果它完成了工作,那就这样吧。
由于我们不知道您的具体问题,我只能根据您告诉我们的情况给出建议。你说有一个多态基类型的容器。例如:
std::vector<Base*> bases;
如果您要使用以特定于派生的方式放置到此容器中的任何对象,那么它实际上不是基础对象的容器吗?拥有指向Base
个对象的容器的重点是,您可以迭代它们并将它们全部视为Base
个对象。如果您必须dynamic_cast
到某种Derived
类型,那么您就滥用了Base*
s的多态行为。
如果Derived
继承自Base
,则Derived
是-a Base
。简而言之,如果您使用指向Base
类型的多态Derived
指针,那么您应该只使用Derived
的特征使其成为Base
。