Как я могу избежать dynamic_cast в моем коде C++?
Допустим, у меня есть следующая структура класса:
class Car;
class FooCar : public Car;
class BarCar : public Car;
class Engine;
class FooEngine : public Engine;
class BarEngine : public Engine;
Давайте также дать Car
ручка к его Engine
, FooCar
будет создан с FooEngine*
и BarCar
будет создан с BarEngine*
, Есть ли способ организовать вещи так FooCar
объект может вызывать функции-члены FooEngine
без уныния?
Вот почему структура классов выложена так, как сейчас:
- Все
Car
сEngine
, Далее,FooCar
будет только когда-либо использоватьFooEngine
, - Есть данные и алгоритмы, используемые всеми
Engine
Я бы предпочел не копировать и не вставлять. - Я мог бы хотеть написать функцию, которая требует
Engine
знать о своемCar
,
Как только я набрал dynamic_cast
при написании этого кода я знал, что, вероятно, что-то не так делаю. Есть лучший способ сделать это?
ОБНОВИТЬ:
Основываясь на ответах, данных до сих пор, я склоняюсь к двум возможностям:
- Есть
Car
обеспечить чистый виртуальныйgetEngine()
функция. Это позволило быFooCar
а такжеBarCar
иметь реализации, которые возвращают правильный видEngine
, - Поглотить все
Engine
функциональность вCar
наследственное дерево.Engine
был разбит по причинам технического обслуживания (чтобы сохранитьEngine
вещи в отдельном месте). Это компромисс между большим количеством маленьких классов (небольших по строкам кода) и меньшим количеством больших классов.
Есть ли сильное предпочтение сообщества одному из этих решений? Есть ли третий вариант, который я не рассматривал?
10 ответов
Я предполагаю, что Автомобиль держит указатель Двигателя, и именно поэтому вы чувствуете себя подавленным.
Уберите указатель из вашего базового класса и замените его чисто виртуальной функцией get_engine(). Тогда ваш FooCar и BarCar могут удерживать указатели на правильный тип двигателя.
(Редактировать)
Почему это работает:
Поскольку виртуальная функция Car::get_engine()
будет возвращать ссылку или указатель, C++ позволит производным классам реализовать эту функцию с другим типом возвращаемого значения, при условии, что возвращаемый тип отличается только тем, что является более производным типом.
Это называется ковариантным типом возврата и позволит каждому Car
введите, чтобы вернуть правильный Engine
,
Я хотел бы добавить одну вещь: этот дизайн уже плохо пахнет для меня из-за того, что я называю параллельными деревьями.
В основном, если вы в конечном итоге получаете параллельные иерархии классов (как у вас с Car и Engine), то вы просто напрашиваетесь на неприятности.
Я бы переосмыслил, если Engine (и даже Car) должны иметь подклассы или это просто разные экземпляры одних и тех же соответствующих базовых классов.
Вы также можете шаблонизировать тип двигателя следующим образом
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 для создания правильного автомобильного объекта с правильным движком.
Вы можете хранить 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
потребует удручения.
COM-компонент Microsoft немного неуклюж, но у него действительно новая концепция - если у вас есть указатель на интерфейс объекта, вы можете запросить его, чтобы узнать, поддерживает ли он какие-либо другие интерфейсы, используя функцию QueryInterface. Идея состоит в том, чтобы разбить ваш класс Engine на несколько интерфейсов, чтобы каждый из них мог использоваться независимо.
Я думаю, что это зависит, если Engine
используется только в личных целях Car
и его дети или если вы также хотите использовать его в других объектах.
Если Engine
функциональные возможности не являются специфичными для Car
s, я бы использовал virtual Engine* getEngine()
метод вместо хранения указателя в базовом классе.
Если его логика специфична для Car
с, я бы предпочел положить общий Engine
данные / логика в отдельном объекте (не обязательно полиморфном) и хранить FooEngine
а также BarEngine
реализация в их соответствующих Car
детский класс.
Когда переработка реализации более необходима, чем наследование интерфейса, состав объектов часто предлагает большую гибкость.
Если я что-то не пропустил, это должно быть довольно тривиально.
Лучший способ сделать это - создать чистые виртуальные функции в Engine, которые затем требуются в производных классах, для которых требуется создать экземпляр.
Дополнительным кредитным решением, вероятно, будет наличие интерфейса IEngine, который вы вместо этого передаете в свой автомобиль, и каждая функция в IEngine является чисто виртуальной. У вас может быть "BaseEngine", который реализует некоторую функциональность, которую вы хотите (то есть, "разделяемую"), а затем иметь листовые.
Интерфейс хорош, если у вас когда-нибудь есть что-то, что вы хотите "выглядеть" как движок, но, вероятно, это не так (например, тестовые классы и тому подобное)
Есть ли способ упорядочить вещи так, чтобы объект 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)
{}
};