Как я могу избежать dynamic_cast в моем коде на C ++?
-
19-08-2019 - |
Вопрос
Допустим, у меня есть следующая структура класса:
class Car;
class FooCar : public Car;
class BarCar : public Car;
class Engine;
class FooEngine : public Engine;
class BarEngine : public Engine;
Давайте также дадим Car
ручка для его Engine
.A FooCar
будет создан с помощью FooEngine*
и a BarCar
будет создан с помощью BarEngine*
.Есть ли способ устроить все так, чтобы FooCar
объект может вызывать функции-члены FooEngine
без понижающего вещания?
Вот почему структура классов устроена так, как она устроена прямо сейчас:
- Все
Car
у них естьEngine
.Кроме того, aFooCar
будет когда-либо использовать толькоFooEngine
. - Существуют данные и алгоритмы, общие для всех
Engine
это то, что я бы предпочел не копировать и не вставлять. - Возможно, я захочу написать функцию, которая требует
Engine
знать о егоCar
.
Как только я набрал dynamic_cast
когда я писал этот код, я знал, что, вероятно, делаю что-то неправильно.Есть ли лучший способ сделать это?
Обновить:
Основываясь на ответах, данных до сих пор, я склоняюсь к двум возможностям:
- Иметь
Car
обеспечьте чистый виртуальныйgetEngine()
функция.Это позволило быFooCar
иBarCar
иметь реализации, которые возвращают правильный видEngine
. - Поглотите все
Engine
функциональность вCar
дерево наследования.Engine
был выведен из строя по причинам технического обслуживания (чтобы сохранитьEngine
вещи в отдельном месте).Это компромисс между наличием более мелких классов (небольших по строкам кода) и меньшим количеством больших классов.
Отдает ли сообщество предпочтение одному из этих решений?Есть ли третий вариант, который я еще не рассматривал?
Решение
Я предполагаю, что у этой Машины есть указатель двигателя, и именно поэтому вы обнаруживаете, что снижаете скорость.
Извлеките указатель из вашего базового класса и замените его чисто виртуальной функцией get_engine().Тогда ваши FooCar и BarCar смогут содержать указатели на правильный тип двигателя.
(Редактировать)
Почему это работает:
Поскольку виртуальная функция Car::get_engine()
вернул бы a ссылка или указатель, C ++ позволит производным классам реализовать эту функцию с другой возвращаемый тип, при условии, что возвращаемый тип отличается только тем, что является более производным типом.
Это называется ковариантные возвращаемые типы, и позволит каждому Car
введите, чтобы вернуть правильный Engine
.
Другие советы
Просто одна вещь, которую я хотел бы добавить:этот дизайн уже неприятно пахнет для меня из-за того, что я называю параллельные деревья.
По сути, если вы в конечном итоге получаете параллельные иерархии классов (как в случае с автомобилем и двигателем), то вы просто напрашиваетесь на неприятности.
Я бы переосмыслил, должен ли двигатель (и даже автомобиль) иметь подклассы или все это просто разные экземпляры одних и тех же соответствующих базовых классов.
Вы также могли бы шаблонизировать тип движка следующим образом
template<class EngineType>
class Car
{
protected:
EngineType* getEngine() {return pEngine;}
private:
EngineType* pEngine;
};
class FooCar : public Car<FooEngine>
class BarCar : public Car<BarEngine>
Я не понимаю, почему автомобиль не может состоять из двигателя (если BarCar всегда будет содержать BarEngine).Двигатель имеет довольно прочную связь с автомобилем.Я бы предпочел:
class BarCar:public Car
{
//.....
private:
BarEngine engine;
}
Возможно ли, чтобы FooCar использовал BarEngine?
Если нет, вы можете захотеть использовать AbstractFactory для создания правильного объекта car с правильным двигателем.
Вы можете хранить FooEngine в FooCar, BarEngine в BarCar
class Car {
public:
...
virtual Engine* getEngine() = 0;
// maybe add const-variant
};
class FooCar : public Car
{
FooEngine* engine;
public:
FooCar(FooEngine* e) : engine(e) {}
FooEngine* getEngine() { return engine; }
};
// BarCar similarly
Проблема с этим подходом заключается в том, что получение движка - это виртуальный вызов (если вас это беспокоит), и метод для настройка двигатель в Car
потребовалось бы понижение частоты.
Я думаю, что это зависит от того, будет ли Engine
используется только в частном порядке Car
и его дочерние элементы или если вы также хотите использовать его в других объектах.
Если Engine
функциональные возможности не являются специфичными для Car
s, я бы использовал virtual Engine* getEngine()
метод вместо сохранения указателя в базовом классе.
Если его логика специфична для Car
s, я бы предпочел поместить общий Engine
данные / логику в отдельном объекте (не обязательно полиморфном) и сохранить FooEngine
и BarEngine
реализация в их соответствующих Car
дочерний класс.
Когда повторное использование реализации более необходимо, чем наследование интерфейса, композиция объектов часто обеспечивает большую гибкость.
COM от Microsoft довольно неуклюж, но у него есть новая концепция - если у вас есть указатель на интерфейс объекта, вы можете запросить его, чтобы узнать, поддерживает ли он какие-либо другие интерфейсы, используя Интерфейс запроса функция.Идея состоит в том, чтобы разбить ваш класс Engine на несколько интерфейсов, чтобы каждый из них можно было использовать независимо.
Учитывая, что я ничего не пропустил, это должно быть довольно тривиально.
Лучший способ сделать это - создать чисто виртуальные функции в движке, которые затем потребуются в производных классах, которые должны быть созданы.
Дополнительным кредитным решением, вероятно, было бы иметь интерфейс под названием IEngine, который вы вместо этого передаете своему автомобилю, и каждая функция в IEngine является чисто виртуальной.У вас мог бы быть 'BaseEngine', который реализует некоторые из функций, которые вы хотите (например, 'shared'), а затем иметь конечные из них.
Интерфейс хорош, если у вас когда-нибудь будет что-то, что вы хотите "выглядеть" как движок, но, вероятно, это не так (например, макеты тестовых классов и тому подобное).
Есть ли способ упорядочить вещи таким образом, чтобы объект FooCar мог вызывать функции-члены FooEngine без понижения качества?
Вот так:
class Car
{
Engine* m_engine;
protected:
Car(Engine* engine)
: m_engine(engine)
{}
};
class FooCar : public Car
{
FooEngine* m_fooEngine;
public:
FooCar(FooEngine* fooEngine)
: base(fooEngine)
, m_fooEngine(fooEngine)
{}
};