Безопасное разыменование указателя в C++

В нашей кодовой базе есть много подобных конструкций:

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. Связанный вопрос (без удовлетворительного ответа): Существует ли C++ эквивалент исключения NullPointerException< /а>


person Mikhail    schedule 18.03.2015    source источник
comment
Есть ли вообще какой-нибудь код, который фактически имеет дело с nullptr случаем? Например. что-то вроде if ( !obj ) { std::cout << "Null, but still okay, moving on with something meaningful\n"; } else { std::cout << "Non-Null, doing something else\n"; }. Или случай nullptr всегда является выходом, потому что это неверный путь?   -  person stefan    schedule 18.03.2015
comment
Этот код существует, но встречается редко. В большинстве случаев мы просто не можем продолжать. Мы ничего не можем сделать. Вся рутина бессмысленна. Так может быть, существование этого объекта является предварительным условием? И мне нужно проверить это, а затем вручную разыменовать указатель, не беспокоясь?   -  person Mikhail    schedule 18.03.2015
comment
Именно это вам и нужно для себя уточнить, имхо. Если это предварительное условие, возможно, лучше подойдет assertion. В противном случае это явно исключительная вещь (0,01% довольно исключительная), поэтому exception подходит. Афаик, в библиотеке такого нет.   -  person stefan    schedule 18.03.2015
comment
Я думаю, вам следует уточнить эту тему и опубликовать ее как ответ.   -  person Mikhail    schedule 18.03.2015
comment
Что, если вы вызовете исключение из функции getObjectThatMayVeryRarelyBeNull?   -  person zapredelom    schedule 18.03.2015
comment
@zapredelom Я не могу изменить эту функцию.   -  person Mikhail    schedule 18.03.2015
comment
И... как насчет окончания срока службы этого объекта? Требуется ли удаление указателя (или какой-либо другой метод удаления)? Если это так, то превращение этого в ссылку скрывает проблему продолжительности жизни (возможно, ведущую к утечкам).   -  person Andre Kostur    schedule 18.03.2015
comment
Что касается указателя, возможно, оставить его как auto *? Я бы предложил auto, но хотел подчеркнуть, что это указатель. Или, может быть, лучше, std::unique_ptr с соответствующим удалением? Но если звонящий не несет ответственности за какие-либо решения о продолжительности жизни, то, вероятно, лучше всего будет использовать auto &.   -  person Andre Kostur    schedule 18.03.2015
comment
@AndreKostur Срок службы объекта не имеет значения в этом вопросе. Объект гарантированно переживет любую рассматриваемую процедуру.   -  person Mikhail    schedule 18.03.2015


Ответы (2)


Есть два способа справиться с проблемой «редкого случая нулевого указателя». Во-первых, это предлагаемое решение исключения. Для этого хорошо подходит метод deref_or_throw, хотя я бы предпочел генерировать исключение runtime_error, а не исключение invalid_argument. Вы также можете назвать его NullPointerException, если хотите.

Однако, если случай ptr != nullptr на самом деле является предварительным условием для алгоритма, вы должны сделать все возможное, чтобы достичь 100% безопасности при наличии этого ненулевого случая. В данном случае подходит assert.

Преимущества assert:

  • Бесплатно во время выполнения в режиме выпуска
  • Дает разработчику кристально ясно понять, что он несет ответственность за то, чтобы этого никогда не произошло.

Недостатки assert:

  • Возможное неопределенное поведение в режиме выпуска

Вам также следует подумать о написании метода getObjectThatMayNeverBeNullButDoingTheSameAsGetObjectThatMayVeryRarelyBeNull(): метода, который гарантированно возвращает ненулевой указатель на объект, который в остальном полностью эквивалентен исходному методу. Однако, если вы можете сделать все возможное, я настоятельно рекомендую не возвращать необработанный указатель. Используйте С++ 11 и возвращайте объекты по значению :-)

person stefan    schedule 18.03.2015
comment
Я хотел бы иметь утверждения, которые не удаляются в выпуске. И я хотел бы различать утверждения и предварительные условия. Есть хорошие ссылки по этой теме? - person Mikhail; 18.03.2015
comment
Утверждения — это предварительные условия (или, по крайней мере, самое близкое к этому). Но ничто не мешает вам использовать и утверждение, и исключение. :) - person stefan; 18.03.2015
comment
Да, решения настолько очевидны, что я уверен, что кто-то хорошо поработал над этим и у него есть последовательная и общая структура. Я не знаю ни одного, хотя. Например, Boost.assert не имеет предварительных условий. - person Mikhail; 19.03.2015

Вы можете и, вероятно, должны проектировать с учетом null случаев. Например, в вашей проблемной области, когда имеет смысл, чтобы методы класса возвращали null? Когда имеет смысл вызывать функции с null аргументами?

Вообще говоря, может быть полезно удалить null ссылки из кода, когда это возможно. Другими словами, вы можете запрограммировать инвариант, что "это не будет нулевым здесь... или там". Это имеет много преимуществ. Вам не придется запутывать код, заключая методы в deref_or_throw. Вы можете добиться большего семантического значения от кода, потому что, как часто вещи null в реальном мире? Ваш код может быть более читабельным, если вы используете исключения для обозначения ошибок, а не null значений. И, наконец, вы уменьшаете потребность в проверке ошибок, а также снижаете риск ошибок во время выполнения (ужасное разыменование нулевого указателя).

Если ваша система не была разработана с учетом null случаев, я бы сказал, что лучше оставить ее в покое и не сходить с ума, оборачивая все deref_or_throw. Возможно, используйте Agile-подход. Во время написания кода проверяйте контракты, которые объекты вашего класса предлагают в качестве сервисов. Как часто разумно ожидать, что эти контракты будут возвращать null? Каково семантическое значение null в этих случаях?

Вероятно, вы могли бы определить классы в вашей системе, которые, как можно разумно ожидать, будут возвращать null. Для этих классов null может быть допустимой бизнес-логикой, а не случайными случаями, указывающими на ошибки реализации. Для классов, которые могут возвращать null, дополнительная проверка может быть полезной. В этом смысле проверка null больше похожа на бизнес-логику, чем на низкоуровневые детали реализации. Тем не менее, повсеместно систематическая обертка всех указателей в deref_or_throw может быть лекарством, которое само по себе является ядом.

person It's Your App LLC    schedule 18.03.2015
comment
Функция getObjectThatMayVeryRarelyBeNull() должна иметь возможность возвращать null. Это редко, но возможно. Но так как это редко, большинство подпрограмм (за исключением некоторых специальных) ожидают, что он вернет объект. В противном случае они просто бессмысленны. - person Mikhail; 18.03.2015
comment
У меня возникло бы искушение удалить null как допустимое возвращаемое значение и, возможно, рассмотреть другой вариант, указывающий на этот очень редкий случай. Не зная вашего варианта использования, я не могу точно предположить, что это будет. Похоже, ваш класс может вернуть неожиданный результат, который не все зависимые обработают должным образом, поэтому действительно опасно возвращать null там. И, конечно же, будущие читатели вашего кода не должны замечать тонкости, что getObjectThatMayVeryRarelyBeNull() возвращает null в 0,01% случаев. - person It's Your App LLC; 18.03.2015
comment
Это действительно сложный вопрос дизайна, и поэтому я его не задаю :) Я ограничиваю область возможных решений, чтобы интерфейс getObjectThatMayVeryRarelyBeNull() нельзя было изменить. - person Mikhail; 18.03.2015
comment
@Mikhail интерфейс getObjectThatMayVeryRarelyBeNull() не может быть изменен Хорошо, но... можно ли внутри функции проверить, будет ли она возвращать null, и в этом случае вызвать исключение внутри функции? - person PaperBirdMaster; 18.03.2015
comment
@PaperBirdMaster Это возможно, но это критическое изменение. Потому что на самом деле это является изменением интерфейса (хотя и бинарно-совместимым), потому что мы удаляем неявный noexcept. - person Mikhail; 18.03.2015
comment
@mikhail Вы упомянули, что большинство подпрограмм ожидают, что они вернут объект. Если nullptr является допустимым возвратом, и эти функции не работают с ним, то нарушены именно эти функции. Неважно, что это редкость. Это может случиться, поэтому вы должны иметь дело с этим. В частности, поскольку последствия его игнорирования — разыменование нулевых указателей и, следовательно, неопределенное поведение. - person Andre Kostur; 18.03.2015
comment
@AndreKostur Эти функции не сломаны, они применимы только в наиболее распространенных сценариях. - person Mikhail; 18.03.2015