Этот код, как представляется, обеспечивает возврат нулевой ссылки в С++

Знание С++ немного по частям. Я переработал какой-то код на работе. Я изменил функцию, чтобы вернуть ссылку на тип. Внутри я просматриваю объект на основе переданного идентификатора, а затем возвращаю ссылку на объект, если он найден. Конечно, я столкнулся с вопросом о том, что вернуть, если я не нахожу объект, и, оглядываясь по Сети, многие утверждают, что возвращение "нулевой ссылки" на С++ невозможно. Основываясь на этом совете, я попробовал трюк о возврате успеха /fail boolean и внесении ссылки на объект в параметр out. Тем не менее, я столкнулся с препятствием, требующим инициализации ссылок, которые я передал бы как фактические параметры, и, конечно, нет никакого способа сделать это. Я отступил к обычному подходу, просто вернув указатель.

Я спросил об этом коллегу. Он часто использует следующий трюк, который принимается как последней версией компилятора 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
}
...

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

Теперь у меня был курс С++ несколько лет назад, и в нем мы подчеркнули, что на С++ все это типы, поэтому я стараюсь держать это в уме, когда все обдумывает. Деконструкция выражения: "static_cast <MyType> (0);", мне действительно кажется, что мы принимаем буквальный нуль, передаем его указателю на MyType (который делает его нулевым указателем), а затем применяем оператор разыменования в контекст назначения ссылочному типу (возвращаемый тип), который должен дать мне ссылку на тот же объект, на который указывает указатель. Это похоже на возвращение нулевой ссылки на меня.

Любые советы, объясняющие, почему это работает (или почему это не должно), будут очень признательны.

Спасибо, Chuck

Ответ 1

Этот код не работает, хотя он может работать. Эта строка разыскивает нулевой указатель:

return *static_cast<MyType*>(0);

Ноль, отбрасываемый в тип указателя, приводит к нулевому указателю; этот нулевой указатель затем разыменовывается с помощью унарного *.

Разыменование нулевого указателя приводит к поведению undefined, поэтому ваша программа может что-то сделать. В примере, который вы описываете, вы получаете "нулевую ссылку" (или, похоже, вы получаете нулевую ссылку), но было бы также разумно, чтобы ваша программа потерпела крах или что-то еще произошло.

Ответ 2

Я согласен с другими плакатами в том, что поведение вашего примера undefined и действительно не должно использоваться. Здесь я предлагаю несколько альтернатив. У каждого из них есть плюсы и минусы.

  • Если объект не найден, выведите исключение, которое попадает в вызывающий уровень.
  • Создайте глобально доступный экземпляр MyType, который является простым объектом оболочки (т.е. static const MyType BAD_MYTYPE) и может использоваться для представления плохого объекта.
  • Если возможно, что объект не будет найден часто, то, возможно, передать объект по ссылке в качестве параметра и вернуть код bool или другой код ошибки, указывающий на успех/сбой. Если он не может найти объект, вы просто не назначаете его в функции.
  • Вместо этого используйте указатели и проверьте на возврат 0.
  • Используйте интеллектуальные указатели Boost, которые позволяют проверять достоверность возвращаемого объекта.

Мое личное предпочтение было бы для одной из первых трех.

Ответ 3

Это поведение undefined. Поскольку это поведение undefined, оно может "работать" с вашим текущим компилятором, но оно может сломаться, если вы когда-либо обновляете/изменяете свой компилятор.

Из спецификации С++ 03:

8.3.2/4... Ссылка должна быть инициализирована для ссылки на действительный объект или функцию. [Обратите внимание: в частности, нулевая ссылка не может существовать в четко определенной программе, так как единственным способом создания такой ссылки было бы привязать ее к "объекту", полученному разыменованием нулевого указателя, что вызывает поведение undefined.

Ответ 4

Если вы состоите в браке с этим интерфейсом обратной ссылки, то Right Thing®  было бы выбросить исключение, если вы не можете найти объект для данного идентификатора. По крайней мере таким образом ваш бедный пользователь может уловить состояние с помощью улова.

Если вы вынесете на них нулевой указатель, у них нет определенного способа обработки ошибки.

Ответ 5

Возврат строки *static_cast<MyType*>(0); вызывает разыменование нулевого указателя, который вызывает поведение undefined.

Ответ 6

Хорошо, ты сам сказал это в названии своего вопроса. Код появляется для работы с нулевой ссылкой.

Когда люди говорят, что нулевые ссылки не существуют в С++, это не означает, что компилятор будет генерировать сообщение об ошибке, если вы попытаетесь его создать. Как вы узнали, вам нечего мешать создавать ссылку с нулевого указателя.

Это просто означает, что в стандарте С++ не указывается, что должно произойти, если вы это сделаете. Нулевые ссылки невозможны, потому что стандарт С++ говорит, что ссылки не должны создаваться из нулевых указателей. (но ничего не говорится о генерации ошибки компиляции, если вы попытаетесь это сделать.)

Это поведение undefined. На практике, поскольку ссылки обычно реализуются как указатели, обычно это работает, если вы пытаетесь создать ссылку из нулевого указателя. Но нет никакой гарантии, что это сработает. Или что он будет работать завтра. Это невозможно в С++, потому что если вы это сделаете, поведение, указанное С++, больше не применяется. Ваша программа может что-то сделать

Все это может показаться немного гипотетическим, потому что, похоже, все работает отлично. Но имейте в виду, что только потому, что он работает, и имеет смысл, что он работает, когда он наивно компилируется, он может сломаться, когда компилятор пытается применить некоторую оптимизацию или другую. Когда компилятор видит ссылку, она гарантируется языковыми правилами, что не является нулевым. Итак, что произойдет, если компилятор использует это предположение для ускорения кода, и вы идете за его спину, создавая "нулевую ссылку"?

Ответ 7

Как уже говорили другие, код, к сожалению, работает только... однако, более принципиально, вы нарушаете контракт здесь.

В С++ ссылка предназначена для псевдонима для объекта. Основываясь на сигнатуре вашей функции, я бы никогда не ожидал, что будет передана ссылка "NULL", и я бы, конечно, НЕ тестировал ее перед ее использованием. Как сказал Джеймс Макнеллис, создание ссылки NULL - это поведение undefined, однако в большинстве случаев это поведение только ползет, когда вы на самом деле пытаетесь его использовать, и теперь вы подвергаете пользователей ваших методов неприятным/хитрым гвоздям ошибок.

Я больше не буду вдаваться в эту проблему, просто укажу вам на Herb Sutter pick в этом вопросе.

Теперь для решения вашей проблемы.

Очевидное решение, конечно, является простым указателем. Люди ожидают, что указатель будет нулевым, поэтому они будут проверять его, когда он вернется к ним (и если они не ошибаются, они ленивы).

Существуют и другие альтернативы, но в основном они сводятся к специальному значению, указывающему на ваш отказ, и нет смысла использовать сложные проекты только ради него...

Последней альтернативой является использование исключений здесь, однако я сам являюсь частичным к совету. Исключения предназначены для исключительных ситуаций, которые вы видите, и для функции find/search это действительно зависит от ожидаемого результата:

  • если вы внедряете внутренний factory, где вы регистрируете модули, а затем вызываете их позже, то невозможность извлечь один модуль указывает на ошибку в программе и как таковое сообщается об исключении, /li >
  • если вы внедряете поисковую систему для своей базы данных, таким образом, имея дело с пользовательским вводом, то не получается найти результат, соответствующий входным критериям, и, следовательно, я бы не использовал исключения в этом случае, не ошибка программирования, но совершенно нормальный курс

Примечание: другие способы включают

  • Boost.Optional, хотя я нахожу это неуклюжим, чтобы обернуть ссылку с ним.
  • Общий указатель или слабый указатель, чтобы контролировать/контролировать время жизни объекта, если он может быть удален, пока вы все еще используете его... мониторинг не работает сам по себе в многопоточной среде, хотя
  • Значение оповещения (обычно объявляется как static const), но оно работает только в том случае, если ваш объект имеет значимое "плохое" или "нулевое" значение. Это, конечно, не тот подход, который я бы рекомендовал, поскольку вы снова даете объект, но он взрывается в руке пользователей, если они что-то делают с ним.

Ответ 8

Как уже упоминалось, ваш код ошибочен, так как он разделяет нулевой указатель.

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

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

Единственный способ убедиться, что ваш код верен, - иметь тестовые окна со 100% -ным охватом, что также означает тестирование обработки ошибок, что в вашем примере, вероятно, вызовет ошибку сегментации в вашей программе.