Подтвердить что ты не робот

Как я могу избежать dynamic_cast в моем коде на С++?

Скажем, у меня есть следующая структура классов:

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*, а BarCar будет создан с помощью BarEngine*. Есть ли способ организовать вещи, поэтому объект FooCar может вызывать функции-члены FooEngine без downcasting?

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

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

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

UPDATE:

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

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

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

4b9b3361

Ответ 1

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

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

(Edit)

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

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

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

Ответ 2

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

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

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

Ответ 3

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

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

class FooCar : public Car<FooEngine>

class BarCar : public Car<BarEngine>

Ответ 4

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

class BarCar:public Car
{
   //.....
   private:
     BarEngine engine;
}

Ответ 5

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

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

Ответ 6

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

Ответ 7

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

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

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

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

Ответ 8

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

Ответ 9

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

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

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

Интерфейс хорош, если у вас есть что-то, что вы хотите "выглядеть" как движок, но, вероятно, нет (т.е. макетов тестовых классов и т.д.).

Ответ 10

Есть ли способ организовать вещи, поэтому объект FooCar может вызывать функции-члены FooEngine без downcasting?

Вот так:

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)
  {}
};