Могу ли я использовать что-то кроме dynamic_cast в этом дизайне?

В нашей системе есть

  • несколько типов устройств
  • каждый тип устройства может иметь другой тип конфигурации
  • каждый тип устройства будет собственной библиотекой

Я в ситуации, когда я вынужден использовать dynamic_cast. Мне интересно, есть ли лучший способ спроектировать это?

что у меня есть:

// in common code
class Config {public: virtual ~Config(){} }; 

    class Device {
     protected:
        Config* devConfig;
    protected:
        virtual void createDevConfig() = 0;
    public:
        virtual void display() = 0;
    };

    // Device specific Code
    class A0Device : public Device {
    protected:
        virtual void createDevConfig() { this->devConfig = new A0Config(); }
    public:
        A0Device() { this->createDevConfig(); }

        virtual void display(){
        A0Config* config = dynamic_cast<A0Config*>(this->devConfig);

        if(!config) std::cout << "Null object\n";

        }
    };

    class A0Config : public Config {};

    int main() {
        Device* dev = new A0Device();
        dev->display();
        return 0;
    }

По сути, A0Device имеет свой собственный тип конфигурации: A0Config, который состоит из других членов. A0Device имеет devConfig, определенный как Config* в базовом классе. В A0Device::display() мне нужно получить доступ к объекту devConfig (как тип A0Config). virtual createDevConfig() гарантирует, что объект конфигурации всегда будет типа A0Config в A0Device => Безопасно ли здесь использовать dynamic_cast? Есть ли лучший способ спроектировать это?


person brainydexter    schedule 26.08.2015    source источник
comment
Зачем A0Device базовый класс?   -  person Neil Kirk    schedule 26.08.2015
comment
Если вы не слышали о двойной отправке, посмотрите. Это еще одно возможное решение.   -  person R Sahu    schedule 26.08.2015
comment
Вы можете переместить поле devConfig в поле A0Device, а затем изменить тип на A0Config*, поскольку теперь оно относится только к A0Device.   -  person user253751    schedule 26.08.2015
comment
@NeilKirk SDK полностью функционирует на устройстве, а информация об A0Device и других типах устройств полностью абстрагируется в их собственных библиотеках.   -  person brainydexter    schedule 26.08.2015
comment
Устройство должно вызывать виртуальную функцию в Config. Или указатель Config следует переместить на A0Device. Вы можете сделать производное устройство шаблоном с конфигурацией в качестве параметра.   -  person Neil Kirk    schedule 27.08.2015


Ответы (3)


Если вы можете поддерживать дополнительные (минимальные в системах общего назначения, огромные в системах реального времени/встраиваемых системах) накладные расходы во время выполнения, вы можете безопасно использовать dynamic_cast<>.

Однако вы должны знать об этой «маленькой» детали. Учитывая выражение dynamic_cast<T*>(expr), всякий раз, когда динамический тип *expr не является ни T, ни подклассом T, выражение вычисляется до нулевого указателя, nullptr. И, конечно же, разыменование nullptr вызывает неопределенное поведение и приведет к сбою на большинстве платформ.

Однако, вероятно, не стоит проверять nullptr если и только если вы знаете, что ваш код рухнет в такой ситуации.

Поскольку C++ — это тарелка с супом и мороженым, картофелем и кофе, и, конечно же, с каждой идеей, пришедшей через уши/разум Страуструпа/комитета C++, существует несколько альтернатив:

  • понижающее приведение static_cast<T*>(expr), хотя это имеет те же проблемы, что и приведение чего-то к чему-то другому, чем оно не является.
  • Для людей C, которые сочли это «необходимым», reinterpret_cast<T*>(expr) в этом нет необходимости, но он есть.
  • Если вы оба считаете что-то технически совершенным, если оно работает, и вы знаете все типы, которые объект может иметь во время выполнения в одном месте, вы можете использовать enum for the type с union for holding the data с placement new с explicit destructor calls суперкомбо.
  • Если в проблему вовлечены данные, у вас может быть что-то вроде virtual Config &getConfig() = 0 в базе и что-то вроде ABC123Config config; Config &getConfig { return this->config; } в производной.
  • Только для сумасшедших вроде меня: используйте приведение в стиле C/конструктор.

Надеюсь это поможет!

person 3442    schedule 26.08.2015
comment
@brainydexter: Вы имеете в виду пункт если это связано...? - person 3442; 27.08.2015
comment
Да : virtual Config &getConfig() = 0 in the Base, - person brainydexter; 27.08.2015
comment
Кроме того, на мой вопрос, динамическое литье не будет происходить часто, возможно, во время инициализации, поэтому я не думаю, что затраты времени выполнения будут проблемой. Что касается дизайна, вы думаете, здесь есть проблема? - person brainydexter; 27.08.2015
comment
Выражение virtual Config &getConfig() = 0 объявляет чистую виртуальную функцию с именем getConfig, который возвращает Config& (и, таким образом, возвращаемое значение может ссылаться на данный объект Derived, как я сказал вам в другом комментарии). Спецификатор virtual сообщает компилятору, что динамический тип объекта должен запрашиваться при каждом вызове функции через Base& или Base*. По умолчанию базовый класс определяет функцию, которая будет вызываться, если тип реального объекта не перегружает функцию. (Продолжение следует...) - person 3442; 27.08.2015
comment
@brainydexter: Извините, мне пришлось разрезать комнет на две части из-за его длины. Продолжение: Спецификатор = 0 (называемый чистым спецификатором) помечает виртуальную функцию как чистую. Чистая виртуальная функция не имеет значение по умолчанию для вызова, если функция не перегружена. Таким образом, класс, содержащий чисто виртуальную функцию, не может быть создан (это было бы катастрофой!), и его называют абстрактным. В дереве наследования все подклассы абстрактного Base будут неявно абстрактными до тех пор, пока все чисто виртуальные функции не будут полностью реализованы данным подклассом. - person 3442; 27.08.2015
comment
Давайте продолжим обсуждение в чате. - person brainydexter; 27.08.2015

У вас может быть чистая виртуальная функция в базе, которая возвращает указатель или ссылку Config (что я бы предпочел, если вам не нужен указатель), а затем иметь хранилище в производных классах. Этот вопрос/ответы охватывают различия между указателями и ссылками: Когда использовать ссылки и указатели< /а>

Преимущество этого дизайна в том, что все в базе, которому требуется Config, может использовать getConfig, в то время как все, что нужно использовать производный класс, может без приведения. Кроме того, вам не нужно звонить new и delete.

class Device {
   protected:
      virtual Config& getConfig() = 0;
   ...
};

class A0Device {
public:
   ...
   Config& getConfig() {return config;}
   ...
private:
   A0Config config;
};
person Dominique McDonnell    schedule 26.08.2015
comment
Когда вызывается A0Device getconfig, вы получаете объект конфигурации, а не тип A0Config — верно? - person brainydexter; 26.08.2015
comment
@brainydexter: как указатели, так и ссылки на любой Base могут фактически указывать/ссылаться на данный объект Derived. - person 3442; 27.08.2015
comment
хорошо, но что мне здесь дает ссылка вместо указателя? Эта конфигурация будет содержать множество других структур, которые будут указывать на ОГРОМНЫЕ массивы. Следовательно, я склонялся к использованию указателей - person brainydexter; 27.08.2015
comment
@brainydexter, я предпочитаю ссылки на объекты, а не указатели, если вы не выбираете объект динамически, поскольку его легче читать (без беспорядочных разыменований при доступе), гарантирует, что вы не можете сделать ошибку, например, увеличить указатель (если только вы намеренно делаете что-то вроде взятия адреса, но вы не можете помешать людям намеренно злоупотреблять вещами) и дает больше информации, чем указатель («Это то, что вам нужно», а не «Это одна из потенциально многих вещей») - person Dominique McDonnell; 27.08.2015
comment
@brainydexter, вы можете думать о ссылках как о константных указателях или указателях, которые вы не можете изменить (T * const). Не путайте это с указателями на константные объекты, где вы можете изменить то, на что указывает указатель, но не объект, на который указывает (const T*). Любые преимущества, которые вы получаете от указателей, вы также получаете от ссылок. За исключением возможности выбрать произвольный объект. - person Dominique McDonnell; 27.08.2015
comment
понятно. Спасибо @DominicMcDonnell - person brainydexter; 27.08.2015
comment
Спасибо @KemyLand. Я буду использовать эту ссылку в своем ответе. - person Dominique McDonnell; 27.08.2015
comment
@DominicMcDonnell: Нет проблем :). - person 3442; 27.08.2015
comment
@brainydexter, я только что добавил к своему ответу, чтобы объяснить, почему я решил бы вашу проблему таким образом, если бы вы сами этого не поняли. - person Dominique McDonnell; 27.08.2015
comment
Спасибо @DominicMcDonnell: Да, я вижу выгоду. Я, скорее всего, пойду по этому пути, но мне все равно нужно будет использовать dynamic_cast. В моей кодовой базе устройство имеет блоки (как указатели членов), и блоки не будут иметь ссылки на объект устройства. внутри блока я получаю ссылку на device::config следующим образом: DeviceMgr::instance()->getDevice(deviceId)->getConfig() => в зависимости от deviceType будет возвращен правильный configType - person brainydexter; 27.08.2015

[Не уверен, что я что-то упускаю здесь, но это более или менее похоже на запах дизайна]

Почему A0Config не может агрегировать Config?

Довольно часто появление dynamic_cast указывает на то, что что-то не так с точки зрения установления отношений IS-A. В этом случае A0Configпредоставляет некоторые функции (getA()), которые не вписываются в его базовый класс Config, возможно, он нарушает LSP. -Принцип подстановки Лисков, тем самым нарушая сильный IS-A.

Тем не менее, Если классовые отношения могут быть выражены более чем одним способом, используйте самое слабое из практически применимых отношений — Херб Саттер

... и Шон Пэрент доходит до этого. Наследование — это основной класс зла

person Arun    schedule 26.08.2015