Функциональность против безопасности / Статические и динамические экземпляры

#c #instantiation #simulator

#c #создание экземпляра #тренажёр

Вопрос:

Я нахожусь в ситуации, когда я считаю, что две реализации верны, и я не знаю, какую из них выбрать.

У меня есть приложение, имитирующее считыватели карт. Он имеет графический интерфейс, в котором вы выбираете, какой последовательный порт и скорость использовать, а также кнопку воспроизведения и остановки.

Я ищу лучшую реализацию для построения reader.

У меня есть SimulatorCore класс, который живет до тех пор, пока мое приложение SimulatorCore создает экземпляр Reader класса. И можно будет имитировать несколько считывателей на нескольких последовательных портах.

Две возможности:

  • My Reader — это указатель (динамическое создание экземпляра), я создаю его при нажатии кнопки воспроизведения, удаляю его при нажатии кнопки остановки.

  • Мой Reader объект (статическое создание экземпляра), я создаю его в SimulatorCore конструкторе, затем создаю и вызываю Reader.init() и Reader.cleanup() в свой класс Reader и вызываю их при воспроизведении и остановке

Я лично вижу функциональную сторону, и я явно хочу использовать указатель, и у меня нет экземпляра reader, если не моделируется reader.

Кто-то говорит мне, что я должен использовать статическое создание экземпляра (причина: для безопасности и потому, что «плохо использовать указатель, когда у вас есть выбор не использовать их»)

Я не знаком с ними, но я думаю, что я также могу использовать smart pointer.

Примеры кода: 1-е решение:

 class SimulatorCore
{  
    play(){reader = new Reader();};
    stop(){delete reader; reader = nullptr;};

private:
    Reader *reader;
}
  

Примеры кода: 2-е решение:

 class SimulatorCore
{  
    play(){reader.init();};
    stop(){reader.cleanup();};

private:
    Reader reader;
}
  

Код не протестирован, я просто использовал его для иллюстрации.

Каково наилучшее решение? Почему?

Ответ №1:

Вы можете легко использовать shared_ptr / unique_ptr:

 class SimulatorCore
{  
    play(){_reader = make_shared<Reader>();};
    stop(){_reader = nullptr};

private:
    shared_ptr<Reader> _reader;
}
  

Я думаю, это правильно решит вашу проблему.

Динамическое распределение создает некоторые проблемы, например, с выбросом исключения (например, может произойти потеря памяти, если между play() и stop() возникнет исключение, и stop() никогда не будет вызван). Или вы можете просто забыть где-нибудь вызвать stop() перед уничтожением SimulatorCore, это возможно, если программа тяжелая.

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

Комментарии:

1. Спасибо, я наконец-то использую unique_ptr, и он отлично работает. Я приму другой ответ из-за объяснений, но вы решили мою проблему.

Ответ №2:

Обычно вам следует избегать выполнения динамического выделения new самостоятельно, поэтому, если вы собираетесь использовать 1-е решение, вам следует вместо этого использовать интеллектуальные указатели.

Однако главный вопрос здесь — это вопрос логики. Реальное устройство чтения карт существует в состоянии ожидания, пока оно не используется. Во 2-м решении, что делать init и cleanup делать? Они просто переводят устройство чтения карт в состояние ожидания или начинают имитировать фактическое считывание карты? Если это первый случай, я предполагаю, что такое поведение должно быть в конструкторе и деструкторе Reader , а затем создание Reader объекта означает создание устройства чтения карт. Если это второй случай, то я бы сказал, что 2-е решение в значительной степени правильное, просто функции плохо названы.

Мне кажется наиболее логичным что-то вроде этого:

 class SimulatorCore
{  
    play(){reader.start();};
    stop(){reader.stop();};

private:
    Reader reader;
}
  

Да, все, что я сделал, это изменил имена функций Reader . Однако функции теперь не несут ответственности за инициализацию или очистку считывателя — эта ответственность находится в руках Reader конструктора и деструктора. Вместо start этого и stop начните и завершите моделирование Reader . Затем один Reader экземпляр может входить и выходить из этого режима моделирования несколько раз за время своего существования.

Если позже вы захотите распространить эту идею на несколько Reader s, вы можете просто изменить элемент на:

 std::vector<Reader> readers;
  

Однако я не могу точно знать, что это то, что вы хотите, потому что я не знаю логики вашей программы. Надеюсь, это даст вам несколько идей.

Опять же, что бы вы ни решили сделать, вам следует избегать использования new для выделения ваших Reader s, а затем также избегать использования необработанных указателей для ссылки на эти Reader s. Используйте интеллектуальные указатели и соответствующие make_... им функции для динамического выделения этих объектов.

Комментарии:

1. Действительно хорошее объяснение, спасибо. Я буду использовать smart pointer, я думаю, что это лучшее решение, и мне нужно их практиковать! Кнопка остановки фактически останавливает симуляцию, нет симуляции = нет считывателя, поэтому я думаю, что хорошо удалить считыватель.

2. @MoKaT Хорошо, если это ваша логика, тогда это звучит нормально!

Ответ №3:

Это явно зависит от того, как организована вся ваша программа, но в целом, я думаю, я бы предпочел статический подход из соображений ответственности:

Предположим, у вас есть отдельный класс, который обрабатывает последовательную связь. Этот класс будет отправлять и получать сообщения и отправлять их в класс reader. Сообщение может прийти в любое время. Разница между динамическим и статическим подходами заключается в:

  • При динамическом подходе последовательный класс должен проверить, действительно ли существует средство чтения, прежде чем отправлять сообщение. Или читатель должен зарегистрироваться и отменить регистрацию в последовательном классе.
  • При статическом подходе класс reader может сам решить, способен ли он обработать сообщение в данный момент или нет.

Поэтому я думаю, что статический подход немного проще и понятнее.

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

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

Комментарии:

1. Спасибо, мое общение является частью моего устройства чтения (поскольку мне, возможно, понадобится несколько устройств чтения на разных последовательных портах — USB). И у меня есть несколько классов чтения, но на другом уровне (где у меня есть абстрактный базовый класс, который является моим интерфейсом для создания экземпляров разных читателей). Интеллектуальный указатель кажется идеальным для этого случая.

2. Я упростил свой вопрос, но Reader мы говорим здесь о конечном автомате, который создает экземпляр реального reader. Но это был скорее функциональный вопрос, чем технический.

3. Хорошо, в первой части этого ответа я попытался показать пример, в котором «всегда присутствующее и действительное» свойство статических объектов является преимуществом. Основываясь на том, что вы добавили сюда, я думаю, я бы тоже использовал интеллектуальные указатели.