Должен ли я изменить свой дизайн, чтобы предотвратить динамические броски?

Я прочитал несколько веток о динамическом приведении в C ++, и все люди утверждают, что это указывает на плохой дизайн. На других языках я никогда не задумывался о проверке типа объекта. Я никогда не использую его как альтернативу полиморфизму и только тогда, когда сильная связь кажется вполне приемлемой. Одна из таких ситуаций, с которой я сталкиваюсь довольно часто: наличие списка (я использую std :: vector в C ++) объектов, все они получены из общего базового класса. Список управляется объектом, которому разрешено знать различные подклассы (часто это небольшая иерархия частных классов в классе управляющих объектов). Храня их в одном списке (массив, вектор, …), я все еще могу извлечь выгоду из полиморфизма, но когда операция предназначена для воздействия на объекты определенного подкласса, я использую динамическое приведение или что-то подобное.

Есть ли другой подход к этому типу проблемы без динамического приведения или проверки типов, которые я пропускаю? Мне действительно любопытно, как программисты, которые избегают этого любой ценой, справились бы с ними.

Если мое описание слишком абстрактное, я мог бы написать простой пример на C ++ (Edit: см. Ниже).

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).

8

Решение

По крайней мере, с моей точки зрения, dynamic_cast существует по причине, и бывают случаи, когда разумно его использовать. Это может быть один из тех времен.

Учитывая ситуацию, которую вы описываете, один возможный альтернативой может быть определение большего количества необходимых вам операций в базовом классе, но определите их как (возможно, молча) сбой, если вы вызываете их для базового класса или других классов, которые не поддерживают эти операции.

Реальный вопрос заключается в том, имеет ли смысл определять ваши операции таким образом. Возвращаясь к типичной иерархии на основе животных, если вы работаете с Birds, для Bird часто целесообразно определить fly член, и для немногих птиц, которые не могут летать, просто потерпеть неудачу (теоретически следует переименовать в нечто вроде attempt_to_fly, но это редко делает многое).

Если вы видите многое из этого, это имеет тенденцию указывать на отсутствие абстракции в ваших классах — например, вместо fly или же attempt_to_flyВы, возможно, действительно хотите travel член, и это зависит от конкретного животного, чтобы определить, делать ли это плавать, ползать, ходить, летать и т. д.

8

Другие решения

это

но когда операция предназначена для воздействия на объекты определенного
Подкласс Я использую динамическое приведение или что-то подобное

Похоже, что моделирование объекта не является правильным. У вас есть список, содержащий экземпляры подклассов, но на самом деле они не являются подклассами, поскольку вы не можете воздействовать на них всех одинаково (Лисков и т. Д.).

Одним из возможных решений является расширение базового класса таким образом, чтобы у вас был набор неоперативных методов, которые некоторые подклассы могут переопределять. Но это все еще звучит не совсем правильно.

0

Вопрос довольно широкий, поэтому необходимо учитывать некоторые общие моменты:

  • Это особенность языка по определенной причине, то есть существуют допустимые варианты использования.
  • По сути, это не хорошо и не плохо.
  • Проверьте, является ли это правильным решением для ваш проблема.
  • Знать альтернативы чтобы решить вашу проблему.
  • Сравнение его с другими языками должно включать вопрос о том, возможны ли альтернативы C ++ / C ++ 11 и в этом другом языке.
  • dynamic_cast дает определенную стоимость. Если речь идет о производительности во время выполнения, сравните ее со стоимостью альтернатив.
  • Проверки выполняются во время выполнения, что создает определенный риск того, что вы поставите программное обеспечение с ошибками, если оно не протестировано должным образом. С помощью статической проверки типов, компилятор может помочь вам, даже гарантия, что определенные проблемы / ошибки не возникнут.
0

Обычно в таких ситуациях вы управляете не списком производных объектов, а списком указателей интерфейса (или базовых указателей), которые ссылаются на фактические производные объекты. Всякий раз, когда вы хотите выполнить операцию над конкретным объектом, вы обращаетесь к нему через его интерфейс / базовый класс, который должен предоставлять все необходимые функции. Производные классы должны переопределять открытую базовую функциональность с конкретной реализацией.

0

использование dynamic_cast является признаком желания реализовать шаблон проектирования, такой как Visitor или Command, поэтому я бы рекомендовал рефакторинг, чтобы сделать это очевидным

0

Как и в любом подходе, который считается плохим программированием, всегда будут некоторые исключения. Когда программист говорит, что «то-то и то-то есть зло, и вы никогда не должны этого делать», они действительно имеют в виду, что «почти никогда нет причин использовать то-то и то-то, поэтому вам лучше объяснить себя». Я сам никогда не сталкивался с ситуацией, когда dynamic_cast абсолютно необходимо и не может быть легко реорганизовано. Однако, если он выполнит свою работу, пусть будет так.

Поскольку мы не знаем вашей конкретной проблемы, я могу давать советы только с учетом того, что вы нам сообщили. Вы говорите, есть контейнер полиморфных базовых типов. Например:

std::vector<Base*> bases;

Если вы собираетесь использовать какой-либо из объектов, которые вы помещаете в этот контейнер специфичным для производного образом образом, то это не совсем контейнер базовых объектов, не так ли? Весь смысл наличия контейнера указателей на Base Объекты в том, что вы можете перебирать их и рассматривать их как Base объекты. Если вам нужно dynamic_cast для некоторых Derived типа, то вы злоупотребили полиморфным поведением Base*s.

Если Derived наследуется от Base, затем Derived это Base, Проще говоря, если вы используете полиморфный Base указатель на Derived типа, то вы должны использовать только функции Derived что делает его Base,

0
По вопросам рекламы [email protected]