Gsl:: not_null <T *> vs. std:: reference_wrapper <T> против T &
Основные рекомендации по С++ были представлены недавно (поздравления!), и меня беспокоит тип gsl::not_null
. Как указано в I.12: Объявите указатель, который не должен быть нулевым как not_null
:
Чтобы избежать разыменования ошибок nullptr. Чтобы повысить производительность избегая избыточных проверок для nullptr.
...
Указывая намерение в источник, исполнители и инструменты могут обеспечить лучшую диагностику, такую как нахождение некоторых классов ошибок посредством статического анализа и выполнение оптимизации, такие как удаление ветвей и нулевые тесты.
Цель ясно. Однако у нас уже есть языковая функция. Указатели, которые не могут быть пустыми, называются ссылками. И хотя ссылки не могут восстанавливаться после их создания, эта проблема решается std::reference_wrapper
.
Основное различие между gsl::not_null
и std::reference_wrapper
я вижу в том, что последнее можно использовать только вместо указателей, в то время как первое работает ни на что nullptr
-assignable (цитата из F.17: Используйте not_null, чтобы указать, что значение "null" не является допустимым значением):
not_null
предназначен не только для встроенных указателей. Он работает для array_view
, string_view
, unique_ptr
, shared_ptr
и другие похожие на указатели типы.
Я представляю таблицу сравнения функций следующим образом:
T&
:
- Не удается сохранить
nullptr
? - Да
- Rebindable? - Нет
- Может использоваться вместо чего-то, кроме указателей? - Нет
std::reference_wrapper<T>
:
- Не удается сохранить
nullptr
? - Да
- Rebindable? - Да
- Может использоваться вместо чего-то, кроме указателей? - Нет
gsl::not_null<T*>
:
- Не удается сохранить
nullptr
? - Да
- Rebindable? - Да
- Может использоваться вместо чего-то, кроме указателей? - Да
Теперь вот вопросы, наконец:
- Правильно ли я понимаю различия между этими понятиями?
- Означает ли это, что
std::reference_wrapper
теперь бесполезен?
PS Я создал теги cpp-core-guidelines
и guideline-support-library
для этого, надеюсь, правильно.
Ответы
Ответ 1
Ссылки не являются указателями, которые не могут быть пустыми. Ссылки семантически сильно отличаются от указателей.
Ссылки имеют назначение значений и семантику сравнения; то есть операции присваивания или сравнения, включающие ссылки, считывают и записывают ссылочное значение. Указатели имеют (контринтуитивно) ссылочное задание и семантику сравнения; то есть операции присваивания или сравнения, включающие указатели, считывают и записывают саму ссылку (т.е. адрес ссылочного объекта).
Как вы заметили, ссылки не могут быть отскок (из-за семантики присваивания значения), но шаблон класса reference_wrapper<T>
может быть отскок, потому что он имеет семантику назначения ссылок. Это связано с тем, что reference_wrapper<T>
предназначен для использования с контейнерами и алгоритмами STL и не будет корректно вести себя, если его оператор назначения копирования не сделал то же самое, что и его конструктор копирования. Тем не менее, reference_wrapper<T>
по-прежнему имеет семантику сравнения значений, как ссылку, поэтому при использовании с контейнерами и алгоритмами STL ведет себя по-разному. Например, set<T*>
может содержать указатели на разные объекты с одинаковым значением, а set<reference_wrapper<T>>
может содержать ссылку только на один объект с заданным значением.
Шаблон класса not_null<T*>
имеет ссылочное назначение и семантику сравнения, например указатель; это указательный тип. Это означает, что он работает как указатель при использовании с контейнерами STL и алгоритмами. Он просто не может быть нулевым.
Итак, вы правы в своей оценке, за исключением того, что вы забыли о семантике сравнения. И нет, reference_wrapper<T>
не будет устаревать любым типом типа указателя, потому что он имеет ссылочную семантику сравнения значений.
Ответ 2
Я думаю, что есть еще варианты использования std::reference_wrapper
, которые не покрываются gsl::not_null
. В принципе, std::reference_wrapper
отражает ссылку и имеет преобразование operator T&
, а not_null
имеет интерфейс указателя с operator->
. Один случай использования, который приходит мне на ум сразу, - это создание потока:
void funcWithReference(int& x) { x = 42; }
int i=0;
auto t = std::thread( funcWithReference, std::ref(i) );
Если у меня нет контроля над funcWithReference
, я не могу использовать not_null
.
То же самое относится к функторам для алгоритмов, и мне пришлось использовать его для привязки boost::signals
тоже.
Ответ 3
Вот полезная статья, в которой обсуждаются gsl:not_null
: https://visualstudiomagazine.com/articles/2016/06/01/using-the-not_null-template.aspx
Соответствующая часть:
Многие разработчики стараются использовать ссылки как можно больше, потому что ссылки не могут быть нулевыми, но вы не можете повторно использовать ссылку и получить ее ссылаться на что-то другое, как вы можете с помощью указателя. Так что указатели времени - это то, что вы должны использовать, что возвращает вас ко всем что проверка нулевой точки и повторяющийся страх перед исключением с нулевым указателем.