Как я могу избежать dynamic_cast в моем коде C ++? - PullRequest
18 голосов
/ 20 января 2009

Допустим, у меня есть следующая структура класса:

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 без принижения?

Вот почему структура классов выложена именно так, как сейчас:

  1. Все Car имеют Engine. Кроме того, FooCar будет использовать только FooEngine.
  2. Существуют данные и алгоритмы, общие для всех Engine, которые я бы не стал копировать и вставлять.
  3. Возможно, я захочу написать функцию, которая требует Engine, чтобы узнать о ее Car.

Как только я набрал dynamic_cast при написании этого кода, я понял, что, возможно, что-то делал не так. Есть ли лучший способ сделать это?

UPDATE:

На основании приведенных ответов я склоняюсь к двум возможностям:

  1. У Car есть чисто виртуальная функция getEngine(). Это позволило бы FooCar и BarCar иметь реализации, которые возвращают правильный вид Engine.
  2. Впитывает все функции Engine в дерево наследования Car. Engine был разбит по причинам технического обслуживания (чтобы хранить вещи Engine в отдельном месте). Это компромисс между большим количеством небольших классов (небольших по строкам кода) и меньшим количеством больших классов.

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

Ответы [ 10 ]

24 голосов
/ 20 января 2009

Я предполагаю, что Автомобиль держит указатель Двигателя, и именно поэтому вы чувствуете себя подавленным.

Уберите указатель из вашего базового класса и замените его чисто виртуальной функцией get_engine (). Тогда ваши FooCar и BarCar могут удерживать указатели на правильный тип двигателя.

(Edit)

Почему это работает:

Поскольку виртуальная функция Car::get_engine() будет возвращать ссылку или указатель , C ++ позволит производным классам реализовать эту функцию с другим типом возврата , при условии, что возвращаемый результат Тип отличается только тем, что является более производным типом.

Это называется ковариантными типами возврата и позволяет каждому типу Car возвращать правильный Engine.

10 голосов
/ 20 января 2009

Я хотел бы добавить только одну вещь: этот дизайн уже плохо пахнет для меня из-за того, что я называю параллельные деревья .

В основном, если вы в конечном итоге получаете параллельные иерархии классов (как у вас с машиной и двигателем), то вы просто напрашиваетесь на неприятности.

Я бы переосмыслил, если Engine (и даже Car) должны иметь подклассы или это просто разные экземпляры одних и тех же соответствующих базовых классов.

7 голосов
/ 20 января 2009

Вы также можете шаблонизировать тип двигателя следующим образом

template<class EngineType>
class Car
{
    protected:
        EngineType* getEngine() {return pEngine;}
    private:
        EngineType* pEngine;
};

class FooCar : public Car<FooEngine>

class BarCar : public Car<BarEngine>
4 голосов
/ 20 января 2009

Не понимаю, почему автомобиль не может состоять из двигателя (если BarCar всегда будет содержать BarEngine). Двигатель имеет довольно прочные отношения с автомобилем. Я бы предпочел:

class BarCar:public Car
{
   //.....
   private:
     BarEngine engine;
}
2 голосов
/ 20 января 2009

Вы можете хранить 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 потребует даункастинга.

2 голосов
/ 20 января 2009

Может ли FooCar использовать BarEngine?

Если нет, вы можете использовать AbstractFactory для создания правильного автомобильного объекта с правильным движком.

1 голос
/ 20 января 2009

COM от Microsoft немного неуклюж, но у него есть новая концепция - если у вас есть указатель на интерфейс объекта, вы можете запросить его, чтобы узнать, поддерживает ли он какие-либо другие интерфейсы, используя QueryInterface функция. Идея состоит в том, чтобы разбить ваш класс Engine на несколько интерфейсов, чтобы каждый из них мог использоваться независимо.

1 голос
/ 20 января 2009

Я думаю, это зависит от того, используется ли Engine только в частном порядке Car и его дочерними элементами или если вы также хотите использовать его в других объектах.

Если функции Engine не относятся к Car с, я бы использовал метод virtual Engine* getEngine() вместо хранения указателя в базовом классе.

Если его логика специфична для Car s, я бы предпочел поместить общие данные / логику Engine в отдельный объект (не обязательно полиморфный) и сохранить реализацию FooEngine и BarEngine в их соответствующих Car детский класс.

Когда переработка реализации более необходима, чем наследование интерфейса, состав объектов часто предлагает большую гибкость.

0 голосов
/ 20 января 2009

Есть ли способ упорядочить вещи так, чтобы объект 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)
  {}
};
0 голосов
/ 20 января 2009

Если я что-то не пропустил, это должно быть довольно тривиально.

Лучший способ сделать это - создать чисто виртуальные функции в Engine, которые затем требуются в производных классах, для которых необходимо создать экземпляр.

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

Интерфейс хорош, если у вас есть что-то, что вы хотите «выглядеть» как движок, но, вероятно, это не так (т. Е. Пробные классы и т. Д.).

...