Прежде всего обратите внимание, что это особый случай проверки контракта: вы пишете код, который ничего не делает, кроме проверки во время выполнения, что задокументированный контракт выполнен. Ошибка означает, что некоторый код где-то неисправен.
Я всегда немного сомневаюсь в реализации особых случаев более общей концепции. Проверка контракта полезна, потому что она улавливает ошибки программирования при первом пересечении границы API. Что такого особенного в пустых значениях, что означает, что они - единственная часть контракта, которую вы хотите проверить? Тем не менее,
По вопросу подтверждения ввода:
null является особенным в Java: многие API Java написаны так, что null - единственное недопустимое значение, которое даже можно передать в вызов данного метода. В таких случаях нулевая проверка «полностью проверяет» входные данные, поэтому применяется полный аргумент в пользу проверки контракта.
В C ++, с другой стороны, NULL - это только одно из почти 2 ^ 32 (2 ^ 64 на более новых архитектурах) недопустимых значений, которые может принимать параметр указателя, поскольку почти все адреса не относятся к объектам правильного типа. Вы не можете «полностью проверить» свои входные данные, если у вас нет списка всех объектов этого типа.
Тогда возникает вопрос, является ли NULL достаточно распространенным неверным вводом, чтобы получить специальную обработку, которую (foo *)(-1)
не получает?
В отличие от Java, поля не инициализируются автоматически в NULL, поэтому неинициализированное значение мусора столь же правдоподобно, как и NULL. Но иногда объекты C ++ имеют члены-указатели, которые явно инициализируются NULL, что означает «у меня его еще нет». Если ваш абонент делает это, то существует значительный класс ошибок программирования, которые можно диагностировать с помощью проверки NULL. Исключение может быть проще для их отладки, чем ошибка страницы в библиотеке, для которой у них нет источника. Так что, если вы не возражаете против раздувания кода, это может быть полезно. Но вы должны думать не о себе, а о вызывающем абоненте - это не защитное кодирование, потому что оно «защищает» только от NULL, а не от (foo *) (- 1).
Если NULL не является допустимым вводом, вы можете рассмотреть возможность выбора параметра по ссылке, а не по указателю, но многие стили кодирования не одобряют неконстантные ссылочные параметры. И если вызывающая сторона передает вам * fooptr, где fooptr равен NULL, то это все равно никому не помогло. Что вы пытаетесь сделать, это втиснуть немного больше документации в сигнатуру функции, в надежде, что ваш вызывающий с большей вероятностью подумает: «Хм, fooptr может быть здесь нулевым?» когда они должны явно разыменовать его, чем если бы они просто передали его вам в качестве указателя. Это заходит так далеко, но насколько это может помочь.
Я не знаю C #, но я понимаю, что это похоже на Java в том, что ссылки гарантированно имеют действительные значения (по крайней мере, в безопасном коде), но в отличие от Java в этом не все типы имеют значение NULL. Поэтому я предполагаю, что нулевые проверки там редко стоят того: если вы находитесь в безопасном коде, тогда не используйте обнуляемый тип, если только нулевой не является допустимым вводом, и если вы в небезопасном коде, то применяются те же рассуждения в C ++.
По теме проверки выходных данных:
Похожая проблема возникает: в Java вы можете «полностью проверить» вывод, зная его тип, и что значение не является нулевым. В C ++ вы не можете «полностью проверить» вывод с проверкой NULL - для всего, что вы знаете, функция вернула указатель на объект в своем собственном стеке, который только что был размотан. Но если NULL является распространенным недопустимым возвращением из-за конструкций, обычно используемых автором кода вызываемого абонента, то проверка его поможет.
Во всех случаях:
Используйте утверждения, а не "реальный код", чтобы проверять контракты, где это возможно - после того, как ваше приложение заработало, вам, вероятно, не нужно, чтобы код раздавался у каждого вызываемого, проверяющего все его входные данные, и у каждого вызывающего, проверявшего свои возвращаемые значения. *
В случае написания кода, переносимого на нестандартные реализации C ++, вместо кода в вопросе, который проверяет наличие нуля, а также перехватывает исключение, я бы, вероятно, имел такую функцию:
template<typename T>
static inline void nullcheck(T *ptr) {
#if PLATFORM_TRAITS_NEW_RETURNS_NULL
if (ptr == NULL) throw std::bad_alloc();
#endif
}
Тогда в качестве одного из списка действий, которые вы делаете при портировании на новую систему, вы правильно определяете PLATFORM_TRAITS_NEW_RETURNS_NULL (и, возможно, некоторые другие PLATFORM_TRAITS). Очевидно, вы можете написать заголовок, который делает это для всех известных вам компиляторов. Если кто-то возьмет ваш код и скомпилирует его в нестандартной реализации C ++, о которой вы ничего не знаете, он будет принципиально сам по более важным причинам, чем этот, поэтому ему придется делать это самостоятельно.