Разрушение разметки в С++
В нашей базе кода у нас много таких конструкций:
auto* pObj = getObjectThatMayVeryRarelyBeNull();
if (!pObj) throw std::runtime_error("Ooops!");
// Use pObj->(...)
В 99,99% случаев эта проверка не запускается. Я думаю о следующем решении:
auto& obj = deref_or_throw(getObjectThatMayVeryRarelyBeNull());
// Use obj.(...)
Где deref_or_throw
объявляется следующим образом:
template<class T> T& deref_or_throw(T* p) {
if (p == nullptr) { throw std::invalid_argument("Argument is null!"); }
return *p;
}
Этот код намного яснее и работает по мере необходимости.
Вопрос: я изобретаю колесо? Есть ли какое-то смежное решение в стандартном или импульсном режиме? Или у вас есть некоторые комментарии к решению?
PS. Связанный с этим вопрос (без удовлетворительного ответа): Есть ли эквивалент С++ для исключения NullPointerException
Ответы
Ответ 1
Существует два способа решения проблемы "редкого случая с нулевым указателем". Во-первых, это предлагаемое исключение. Для этого метод deref_or_throw
- это хорошо, хотя я бы предпочел бы выбросить исключение runtime_error
, а не исключение invalid_argument
. Вы также можете подумать о том, чтобы назвать его NullPointerException
, если вы этого предпочтете.
Однако, если случай ptr != nullptr
на самом деле является предварительным условием для алгоритма, вы должны стараться изо всех сил достичь 100% безопасности при наличии этого ненулевого случая. В этом случае подходящей является assert
.
Преимущества assert
:
- Отсутствие затрат во время выполнения в режиме выпуска
- Делает это ясным для разработчика, что он несет ответственность за это, чтобы этого никогда не происходило.
Недостатки assert
:
- Потенциальное поведение undefined в режиме выпуска
Вы также должны рассмотреть возможность написания метода getObjectThatMayNeverBeNullButDoingTheSameAsGetObjectThatMayVeryRarelyBeNull()
: метод, который гарантированно возвращает ненулевой указатель на объект, который в остальном полностью эквивалентен вашему исходному методу. Однако, если вы можете пройти лишнюю милю, я бы настоятельно рекомендовал не возвращать необработанный указатель. Embrace С++ 11 и вернуть объекты по значению: -)
Ответ 2
Вы можете и, вероятно, должны проектировать с учетом случаев null
. Например, в вашей проблемной области, когда имеет смысл возвращать методы класса null
? Когда имеет смысл вызывать функции с аргументами null
?
Вообще говоря, полезно, если возможно, удалять ссылки null
из кода. Другими словами, вы можете запрограммировать на инвариант, что "здесь здесь не будет нуля... или там". Это имеет много преимуществ. Вам не придется обфускать код, обертывая методы в deref_or_throw
. Вы можете получить больше семантического значения из кода, потому что, как часто вещи null
в реальном мире? Ваш код может быть более читаемым, если вы используете исключения для указания ошибок, а не значений null
. И, наконец, вы уменьшаете необходимость в проверке ошибок, а также уменьшаете риск ошибок времени выполнения (разыменованный ужасный нулевой указатель).
Если ваша система не была разработана с учетом случаев null
, я бы сказал, что лучше оставить ее в покое и не сходить с ума, обернув все с помощью deref_or_throw
. Возможно, возьмите подход Agile. Когда вы кодируете, проверьте контракты, которые предлагают объекты класса в качестве сервисов. Как часто можно ожидать, что эти контракты возвратятся null
? Что такое семантическое значение null
в этих случаях?
Вероятно, вы могли бы идентифицировать классы в своей системе, от которых можно было бы ожидать возврата null
. Для этих классов null
может быть действительной бизнес-логикой, а не краткими случаями, которые указывают на ошибки реализации. Для классов, которые, как ожидается, будут возвращены null
, дополнительная проверка может стоить безопасности. В этом смысле проверка null
больше похожа на бизнес-логику, а не на детали реализации низкого уровня. Однако, несмотря на то, что систематическое обертывание всех указаний в deref_or_throw
может быть излечением, которое является его собственным ядом.