С. Мейерс - Эффективный и современный C++ (1114942), страница 15
Текст из файла (страница 15)
Если же doSomeWork использует фигурные скобки, то результатом будет s t d : : vector с двумя элементами. Какойиз этих вариантов корректен! Автор doSomeWork не может этого знать. Это может знатьтолько вызывающий код.Это именно та проблема, которая встает перед функциями стандартной библиотекиs t d : : make_uni que и std : : ma ke_shared (см . раздел 4.4).
Эти функции решают проблему,используя круглые скобки и документируя это решение как части своих интерфейсов'.Следует запомнит ь•Фигурная инициализация является наиболее широко используемым синтаксисоминициализации, предотвращающим сужающие преобразования и нечувствительным к особенностям синтаксического анализа С++.•В процессе разрешения перегрузки конструкторов фигурные инициализаторы соответствуют параметрам std : : i n it i a l i ze r_l i s t , если это возможно, даже еслидругие конструкторы обеспечивают лучшее соответствие.•Примером, в котором выбор между круглыми и фигурными скобками приводит к значительно отличающимся результатам, является создание std : : vесtоr<числовой_тип>с двумя аргументами.•Выбор между круглыми и фигурными скобками для создания объектов внутри шаблонов может быть очень сложным.3 .2.
Предпочитайте nullptr значениям О и NULLДело вот в чем: литерал О представляет собой int, а не указатель. Если С++ встретитв контексте, где может использоваться только указатель, он интерпретирует О как нулевой указатель, но это - запасной выход.
Фундаментальная стратегия С++ состоит в том,что О это значение типа i nt, а не указатель.С практической точки зрения то же самое относится и к NULL. В случае NULL имеется некоторая неопределенность в деталях, поскольку реализациям позволено придаватьО-1Возможен и более гибкий дизайн, который позволяет вызывающим функциям опреденить, должныли использоваться круглые или фигурные скобки в функциях, генерируемых из шаблонов.
Подробности можно найти в записи от 5 июня 20 1 3 года в Andrzej's С++ Ыоg, "Intuitive interface - Part г:3 .2. Предпочитайте nullptr значениям О и NULL69NU11 целочисленный тип, отличный от i nt (например, long). Это не является распространенной практикой, но в действительности не имеет значения, поскольку вопрос нев точном типе NU11, а в том, что ни О, ни NU11 не имеют тип указателя.В С++98 основным следствием этого факта было то, что перегрузка с использованиемтипов указателей и целочисленных типов могла вести к сюрпризам.
Передача О или NU11таким перегрузкам никогда не приводила к вызову функции с указателем:void f ( int ) ;void f ( bool ) ;void f ( void* ) ;11f (0) ;f ( NULL) ;1 1 Вызов f ( int) , не f (void* )/ / Может не компилироваться, но обычно/ / вызывает f ( int ) и никогда - f ( void* )Три nерегрузки функции fНеопределенность в отношении поведения f ( NU11) является отражением свободы, предоставленной реализациям в отношении типа NU11. Если NU11 определен, например, как 0 1(т.е. О как значение типа l ong), то вызов является неоднозначным, поскольку преобразования long в i nt, long в bool и 01 в void* рассматриваются как одинаково подходящие.Интересно, что этот вызов является противоречием между видимым смыслом исходноготекста ("вызываем f с нулевым указателем NU11 ) и фактическим смыслом ("вызываем fс некоторой разновидностью целых чисел - не указателем"). Это противоречащее интуиции поведение приводит к рекомендации программистам на С++98 избегать перегрузкитипов указателей и целочисленных типов.
Эта рекомендация остается в силе и в С++ 1 1 , поскольку, несмотря на рекомендации данного раздела, некоторые разработчики, определенно, продолжат применять О и NU11, несмотря на то что nul lptr является лучшим выбором.Преимущество nullptr заключается в том, что это значение не является значениемцелочисленного типа. Честно говоря, он не имеет и типа указателя, но его можно рассматривать как указатель любого типа.
Фактическим типом nullpt r является std : : nul lpt r_t ,ну, а тип s t d : : nul lptr_t циклически определяется как тип значения nu l lpt r " . Типstd : : nul lptr_ t неявно преобразуется во все типы обычных указателей, и именно это делает nul lptr действующим как указатель всех типов.Вызов перегруженной функции f с nullptr приводит к вызову перегрузки void* (т.е. перегрузки с указателем), поскольку nul lpt r нельзя рассматривать как что-то целочисленное:»11f ( nullptr ) ;Вызов f (void* )Использование nu llptr вместо О или NU11, таким образом, позволяет избежать сюрпризов перегрузки, но это не единственное его преимущество. Оно позволяет также повысить ясность кода, в особенности при применении аutо-переменных.
Предположим,например, что у нас есть следующий исходный текст:auto resultfindRecord ( / * Аргументы * / ) ;i f (result == 0 ) {=70Глава 3. Переход к современному С++Если вы случайно не знаете (или не можете быстро найти), какой тип возвращаетf indRecord, может быть неясно, имеет ли result тип указателя или целочисленный тип.В конце концов, значение О (с которым сравнивается resu lt) может быть в обоих случаях. С другой стороны, если вы увидите кодauto resulti f (result==findRecord ( /* Аргументы * / ) ;nullptr) {то здесь нет никакой неоднозначности: resul t должен иметь тип указателя.Особенно ярко сияет nul l p t r, когда на сцене появляются шаблоны. Предположим,что у вас есть несколько функций, которые должны вызываться только при блокировкесоответствующего мьютекса.
Каждая функция получает указатель определенного вида:/ / Вызывается только приint fl ( std: : shared_ptr<Widget> spw ) ;douЫe f2 ( s td : : unique_ptr<Widget> upw ) ; 1 1 блокировке соответ 1 1 ствующего мьютексаbool f3 (Widget* pw) ;Вызывающий код с передачей нулевых указателей может выглядеть следующим образом:std: : mutex flm, f2m, f3m;// Мьютексы для f l , f2 и f3// C++l l typedef ; см . раздел 3 . 3us ing MuxGuardstd : : lock_guard<std: : mutex> ;=MuxGuard g ( f lm) ;auto resultfl (O) ;=MuxGuard g ( f2m) ;auto resultf2 (NULL ) ;=11Блокировка мьютекса для f lПередача О функции f l1 1 Разблокирование мьютекса1111Блокировка мьютекса для f 2Передача NULL функции f 2/ / Разблокирование мьютекса11MuxGuard g ( f3m) ;1 1 Блокировка мьютекса для f 3auto resultf 3 (nullptr) ; 1 1 Передача nullptr функции f 31 1 Разблокирование мьютекса=То, что в первых двух вызовах не был передан nul lpt r, грустно; тем не менее код работает, а это чего-то да стоит.
Однако повторяющиеся действия еще более грустны. Онипросто беспокоят. Во избежание дублирования такого вида и предназначаются шаблоны,так что давайте превратим эти действия в шаблон.3.2. Предпочитайте nullptr э начениям О и N U L L71template<typename FuncType,t ypename MuxType,t ypename PtrType>auto lockAndCal l ( FuncType func,MuxType& mutex,PtrType ptr) - > decl type ( func (ptr) )using MuxGuard=std : : lock_guard<MuxType> ;MuxGuard g (mutex ) ;return func (ptr) ;Если возвращаемый тип этой функции ( auto .
. . - > de c l t ype ( func ( p t r ) ) заставляет васчесать затылок, обратитесь к разделу 1 .3, в котором объясняется происходящее. Там выузнаете, что в С++ 14 возвращаемый тип можно свести к простому decl t ype ( auto ) :template<typename FuncType,t ypename MuxType ,typename PtrType>decltype (auto ) lockAndCall ( FuncType func, // С++ 1 4MuxType& mutex,PtrType ptr )using MuxGuard=std : : lock_guard<MuxType>;MuxGuard g (mutex ) ;return func (ptr) ;Для данного шаблонаследующий вид:l oc kAndCa l l(любой из версий), вызывающий код может иметьauto resultllockAndCall ( fl , flm, 0 ) ;11 Ошибка !auto resul t2lockAndCal l ( f2 , f2m, NULL ) ;11 Ошибка !auto result ЗlockAndCall ( fЗ , fЗm, nullptr) ; 11 ОКТакой код можно написать, но, как показывают комментарии, в двух случаях из трехэтот код компилироваться не будет. В первом вызове проблема в том, что когда О передается в lockAndC a l l , происходит вывод соответствующего типа шаблона.
Типом О является, был и всегда будет int, как и тип параметра ptr в инстанцировании данного вызоваlockAndC a l l . К сожалению, это означает, что в вызов func в l ockAndCa l l передается int,а этот тип несовместим с параметром std : : shared ptr<Widget>, ожидаемым функциейf l .
Значение О, переданное в вызове lockAndC a l l, призвано представлять нулевой указатель, но на самом деле передается заурядный int. Попытка передать этот int функции f lкак std : : shared_pt r<Widget > представляет собой ошибку типа. Вызов l ockAndCa ll с О72Глава 3. Переход к современному С ++оказывается неудачным, поскольку в шаблоне функции, которая требует аргумент типаstd : : shared_pt r<Widget >, передается значение i nt .Анализ вызова с переданным NULL по сути такой же.