Этот код, кажется, для достижения возврата пустой ссылки в C++
Мои знания C++ несколько пошаговые. Я перерабатывал какой-то код на работе. Я изменил функцию, чтобы вернуть ссылку на тип. Внутри я ищу объект на основе переданного идентификатора, а затем возвращаю ссылку на объект, если найден. Конечно, я столкнулся с вопросом, что возвращать, если я не нахожу объект, и, просматривая веб-страницы, многие люди утверждают, что возвращение "нулевой ссылки" в C++ невозможно. Основываясь на этом совете, я попытался вернуть логическое значение "успех / неудача" и сделать ссылку на объект выходным параметром. Тем не менее, я столкнулся с необходимостью инициализировать ссылки, которые я передал бы в качестве фактических параметров, и, конечно, нет никакого способа сделать это. Я отступил к обычному подходу просто вернуть указатель.
Я спросил коллегу об этом. Он часто использует следующую уловку, которая поддерживается как последней версией компилятора Sun, так и gcc:
MyType& someFunc(int id)
{
// successful case here:
// ...
// fail case:
return *static_cast<MyType*>(0);
}
// Use:
...
MyType& mt = somefunc(myIdNum);
if (&mt) // test for "null reference"
{
// whatever
}
...
Я уже некоторое время поддерживаю эту кодовую базу, но обнаружил, что у меня не так много времени, чтобы просмотреть мелкие детали о языке, как мне хотелось бы. Я копался в своем справочнике, но ответ на этот вопрос ускользает от меня.
Теперь у меня был курс C++ несколько лет назад, и в нем мы подчеркивали, что в C++ все есть типы, поэтому я стараюсь помнить об этом, когда все продумываю. Деконструируя выражение: "static_cast
Любой совет, объясняющий, почему это работает (или почему это не должно), был бы очень признателен.
Спасибо чак
8 ответов
Этот код не работает, хотя может показаться, что он работает. Эта строка разыменовывает нулевой указатель:
return *static_cast<MyType*>(0);
Ноль, приведенный к типу указателя, приводит к нулевому указателю; этот нулевой указатель затем разыменовывается с помощью одинарного*
,
Разыменование нулевого указателя приводит к неопределенному поведению, поэтому ваша программа может делать что угодно. В примере, который вы описываете, вы получаете "нулевую ссылку" (или, кажется, вы получаете нулевую ссылку), но было бы также разумно, чтобы ваша программа вылетала или происходило что-то еще.
Я согласен с другими авторами, что поведение вашего примера не определено и не должно использоваться. Я предлагаю несколько альтернатив здесь. У каждого из них есть свои плюсы и минусы
- Если объект не может быть найден, выдать исключение, которое перехватывается в вызывающем слое.
- Создайте глобально доступный экземпляр
MyType
который является простым объектом оболочки (т.е.static const MyType BAD_MYTYPE
) и может использоваться для представления плохого объекта. - Если существует вероятность, что объект не будет часто обнаруживаться, то, возможно, передайте объект по ссылке в качестве параметра и верните логический или другой код ошибки, указывающий на успех / неудачу. Если он не может найти объект, вы просто не назначаете его в функции.
- Вместо этого используйте указатели и отметьте 0 по возвращении.
- Используйте интеллектуальные указатели Boost, которые позволяют проверять достоверность возвращаемого объекта.
Мое личное предпочтение было бы для одного из первых трех.
Это неопределенное поведение. Поскольку это неопределенное поведение, оно может "работать" на вашем текущем компиляторе, но оно может сломаться, если вы когда-нибудь обновите / измените свой компилятор.
Из спецификации C++03:
8.3.2 / 4... Ссылка должна быть инициализирована для ссылки на действительный объект или функцию. [Примечание: в частности, пустая ссылка не может существовать в четко определенной программе, потому что единственный способ создать такую ссылку - это привязать ее к "объекту", полученному путем разыменования нулевого указателя, что вызывает неопределенное поведение.
Если вы состоите в браке с этим интерфейсом возврата-ссылки, то Right Thing® будет создавать исключение, если вы не можете найти объект для данного идентификатора. По крайней мере, таким образом ваш бедный пользователь может поймать это состояние с помощью улова.
Если вы разыменуете нулевой указатель на них, у них нет определенного способа обработки ошибки.
Ну, вы сами сказали это в заголовке своего вопроса. Код, похоже, работает с нулевой ссылкой.
Когда люди говорят, что в C++ отсутствуют нулевые ссылки, это не означает, что компилятор сгенерирует сообщение об ошибке, если вы попытаетесь его создать. Как вы узнали, ничто не мешает вам создать ссылку из нулевого указателя.
Это просто означает, что стандарт C++ не определяет, что должно произойти, если вы это сделаете. Нулевые ссылки невозможны, потому что стандарт C++ говорит, что ссылки не должны создаваться из нулевых указателей. (но ничего не говорит о генерации ошибки компиляции, если вы пытаетесь это сделать.)
Это неопределенное поведение. На практике, поскольку ссылки обычно реализуются как указатели, обычно кажется, что это работает, если вы пытаетесь создать ссылку из нулевого указателя. Но нет гарантии, что это сработает. Или что это продолжит работать завтра. Это невозможно в C++, потому что если вы сделаете это, поведение, указанное в C++, больше не будет применяться. Ваша программа может делать что угодно
Все это может показаться немного гипотетическим, потому что, похоже, все работает нормально. Но имейте в виду, что только потому, что это работает, и имеет смысл, что это работает, когда наивно компилируется, оно может сломаться, когда компилятор попытается применить некоторую оптимизацию или другое. Когда компилятор видит ссылку, языковые правила гарантируют, что она не равна нулю. Так что же произойдет, если компилятор использует это предположение для ускорения кода, и вы идете за его спиной, создавая "нулевую ссылку"?
Линия возврата *static_cast<MyType*>(0);
разыменовывает нулевой указатель, который вызывает неопределенное поведение.
Как уже говорили другие, код, к сожалению, только работает... однако, что более важно, вы нарушаете контракт здесь.
В C++ ссылка должна быть псевдонимом для объекта. Основываясь на сигнатуре вашей функции, я никогда не ожидал, что ей будет передана ссылка 'NULL', и я определенно НЕ проверю ее перед использованием. Как сказал Джеймс МакНеллис, создание ссылки NULL - это неопределенное поведение, однако в большинстве случаев это поведение проявляется только тогда, когда вы на самом деле пытаетесь его использовать, и теперь вы подвергаете пользователей ваших методов неприятным / хитрым ошибкам.
Я не буду вдаваться в подробности по этому вопросу, просто укажу на выбор Херба Саттера по этому вопросу.
Теперь для решения вашей проблемы.
Очевидное решение, конечно, простой указатель. Люди ожидают, что указатель может быть нулевым, поэтому они проверят его, когда он вернется к ним (и если они этого не сделают, то это их чертовская ошибка за ленивость).
Есть и другие альтернативы, но они в основном сводятся к тому, чтобы иметь специальное значение, указывающее на вашу неудачу, и нет особого смысла использовать сложные конструкции только ради этого...
Последняя альтернатива - использование исключений здесь, однако я сам неравнодушен к совету. Исключения предназначены для исключительных ситуаций, которые вы видите, и для find
/ search
Особенность это действительно зависит от ожидаемого результата:
- если вы реализуете какую-то внутреннюю фабрику, в которой вы регистрируете модули и затем вызываете их позже, то неспособность извлечь один модуль будет указывать на ошибку в программе, и, как таковое, сообщается об исключении.
- если вы реализуете поисковую систему для своей базы данных, таким образом, имея дело с пользовательским вводом, то весьма вероятно, что не найдется результат, соответствующий критериям ввода, и поэтому я не буду использовать исключения в этом случае, поскольку это не программирование. ошибка, но совершенно нормальный курс
Примечание: другие способы включают
- Boost.Optional, хотя я нахожу это неуклюжим, чтобы обернуть ссылку с ним
- Общий указатель или слабый указатель, чтобы контролировать / контролировать время жизни объекта в случае, если он может быть удален, пока вы все еще используете его... мониторинг не работает сам по себе в многопоточной среде, хотя
- Значение часового (обычно объявляемое как static const), но оно работает только в том случае, если ваш объект имеет значимое "плохое" или "нулевое" значение. Это, конечно, не тот подход, который я бы порекомендовал, поскольку вы снова даете объект, но он взрывается в руках пользователя, если они что-то с ним делают
Как уже упоминали другие, ваш код ошибочен, поскольку он разыменовывает нулевой указатель.
Во-вторых, вы используете неверный тип возврата ссылки, возвращая ссылку в вашем примере, как правило, не очень хорошо в C/C++, поскольку это нарушает модель управления памятью языка, на который все указатели ссылаются на адрес памяти. Если вы переписываете код C/C++, который был написан с использованием указателей, в код, который использует ссылки, вы столкнетесь с этими проблемами. Код, использующий указатели, может возвращать указатель NULL, не вызывая проблем, но при возврате ссылки вы должны возвращать что-то, что может быть приведено к выражению 0 или false.
Наиболее важным из всех является шаблон, в котором ваш ошибочный код выполняется только в случае исключения, в данном примере - в случае неудачи. Неправильная обработка ошибок, регистрация ошибок с ошибками и т. Д. Являются самыми катастрофическими ошибками в компьютерных системах, поскольку они никогда не обнаруживаются в удачном случае, но всегда вызывают сбой системы, когда что-то не следует нормальному потоку.
Единственный способ убедиться в правильности вашего кода - это иметь тестовые сценарии со 100-процентным охватом, что означает также тестирование обработки ошибок, что в вашем примере может вызвать ошибку сегментации в вашей программе.