Пропуск "деструкторов" в Си слишком далеко заходит в YAGNI?

9

Я работаю над средним встроенным приложением в C, используя OO-подобные методы. Мои "классы" - это модули .h / .c, использующие структуры данных и структуры указателей функций для эмуляции инкапсуляции, полиморфизма и внедрения зависимостей.

Теперь можно ожидать, что myModule_create(void)функция придет с myModule_destroy(pointer)аналогом. Но когда проект внедряется, ресурсы, которые реально создаются, никогда не должны высвобождаться.

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

Итак, следуя принципу ЯГНИ (тебе это не понадобится), я должен опустить деструкторы? Это кажется мне чрезвычайно странным, но я не могу придумать, как их использовать; ресурсы освобождаются при выключении устройства.

Asics
источник
4
Если вы не предоставляете способ избавиться от объекта, вы передаете четкое сообщение о том, что у них есть «бесконечное» время жизни после создания. Если это имеет смысл для вашего приложения, я говорю: сделайте это.
Гламперт
4
Если вы собираетесь зайти так далеко, связав тип с вашим конкретным вариантом использования, зачем вообще нужна myModule_create(void)функция? Вы можете просто жестко закодировать конкретные экземпляры, которые вы ожидаете использовать в интерфейсе, который вы предоставляете.
Довал
@Doval, я думал об этом. Я стажер, использующий части и куски кода от моего руководителя, поэтому я пытаюсь совмещать «делать все правильно», экспериментировать со стилем ОО в С для опыта и соответствия стандартам компании.
Asics
2
@glampert прибивает это; Я бы добавил, что вы должны сделать ожидаемое бесконечное время жизни чистым в документации функции create.
Blrfl

Ответы:

11

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

Гламперт прав; здесь нет необходимости в деструкторах. Они просто создают раздувание кода и подводный камень для пользователей (использование объекта после вызова его деструктора - неопределенное поведение).

Тем не менее, вы должны быть уверены, что нет необходимости избавляться от объектов. Например, вам нужен объект для UART, который в данный момент не используется?

Деми
источник
3

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

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

Замочить
источник
Обратите внимание, что это не применяется, если вы выполняете выделение только во время запуска, и это шаблон, который я серьезно рассмотрю на устройствах с ограничением памяти.
CodesInChaos
@ Коды: Нет причин ограничивать себя. Хотя вы можете придумать отличный дизайн, который предварительно выделяет память при запуске, когда люди после вас, которые не знакомы с этой грандиозной схемой или не видят ее важность, будут распределять память на летать и там ваш дизайн. Просто сделайте это правильно, выделите / отмените выделение и убедитесь, что то, что вы реализовали, действительно работает. Если у вас действительно есть устройство с ограниченным объемом памяти, то обычно выполняется переопределение нового оператора / malloc и сохранение блоков выделения.
Данк
3

В моей разработке, в которой широко используются непрозрачные типы данных для реализации ОО-подобного подхода, я тоже задавался этим вопросом. Сначала я был решительно настроен уничтожить деструктора с точки зрения YAGNI, а также с точки зрения MISRA «мертвый код». (У меня было много ресурсов, это не было соображением.)

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

Поэтому, по моему опыту, «нет» в YAGNI оказывается «есть», и я закончил тем, что создал деструкторы для каждого класса, думал ли я, нуждаюсь ли я в этом или нет. Даже если я пропущу тестирование, по крайней мере, правильно разработанный деструктор для плохого слоба, который следует за мной, будет иметь его. (И, что гораздо менее важно, он делает код более пригодным для повторного использования, поскольку его можно использовать в среде, где он будет уничтожен.)

Хотя это относится к YAGNI, оно не обращается к мертвому коду. Для этого я обнаружил, что макрос условной компиляции, например, #define BUILD_FOR_TESTING, позволяет исключить деструктор из окончательной производственной сборки.

Делая это следующим образом: у вас есть деструктор для тестирования / повторного использования в будущем, и вы удовлетворяете целям разработки YAGNI и правилам «без мертвых кодов».

Грег Уиллитс
источник
Будьте осторожны с # ifdef'ом вашего кода теста / продукта. Как вы описываете, это достаточно безопасно при применении ко всей функции, потому что если функция действительно нужна, компиляция завершится неудачно. Тем не менее, использование встроенного #ifdef внутри функции намного опаснее, так как вы сейчас тестируете другой путь к коду, чем тот, что работает в prod.
Кевин
0

Вы можете иметь неоперативный деструктор, что-то вроде

  void noop_destructor(void*) {};

затем установить деструктор, Uartвозможно, с помощью

  #define Uart_destructor noop_destructor

(добавьте подходящий состав, если это необходимо)

Не забудьте документировать. Может быть, вы хотите даже

 #define Uart_destructor abort

Альтернативно, особый случай в общем коде, вызывающем деструктор, - это случай, когда функция указателя деструктора NULLдолжна избегать его вызова.

Василий Старынкевич
источник