Эффективный и современный С++. 42 рекомендации по использованию С++11 и С++14 - [27]

Шрифт
Интервал

, а в том, что ни >0, ни >NULL не имеют тип указателя.

В С++98 основным следствием этого факта было то, что перегрузка с использованием типов указателей и целочисленных типов могла вести к сюрпризам. Передача >0 или >NULL таким перегрузкам никогда не приводила к вызову функции с указателем:

>void f(int); // Три перегрузки функции f

>void f(bool);

>void f(void*);


>f(0);        // Вызов f(int), не f(void*)

>f(NULL);     // Может не компилироваться, но обычно

>             // вызывает f(int) и никогда - f(void*)

Неопределенность в отношении поведения >f(NULL) является отражением свободы предоставленной реализациям в отношении типа >NULL. Если >NULL определен, например, как >0L (т.e. >0 как значение типа >long), то вызов является неоднозначным, поскольку преобразования >long в >int, >long в >bool и >0L в >void* рассматриваются как одинаково подходящие. Интересно, что этот вызов является противоречием между видимым смыслом исходного текста (“вызываем >f с нулевым указателем >NULL”) и фактическим смыслом (“вызываем f с некоторой разновидностью целых чисел — не указателем”). Это противоречащее интуиции поведение приводит к рекомендации программистам на С++98 избегать перегрузки типов указателей и целочисленных типов. Эта рекомендация остается в силе и в С++11, поскольку, несмотря на рекомендации данного раздела, некоторые разработчики, определенно, продолжат применять >0 и >NULL, несмотря на то что >nullptr является лучшим выбором.

Преимущество >nullptr заключается в том, что это значение не является значением целочисленного типа. Честно говоря, он не имеет и типа указателя, но его можно рассматривать как указатель любого типа. Фактическим типом >nullptr является >std::nullptr_t, ну, а тип >std::nullptr_t циклически определяется как тип значения >nullptr… Тип >std::nullptr_t неявно преобразуется во все типы обычных указателей, и именно это делает >nullptr действующим как указатель всех типов.

Вызов перегруженной функции >f с >nullptr приводит к вызову перегрузки >void* (т.e. перегрузки с указателем), поскольку >nullptr нельзя рассматривать как что-то целочисленное:

>f(nullptr); // Вызов f(void*)

Использование >nullptr вместо >0 или >NULL, таким образом, позволяет избежать сюрпризов перегрузки, но это не единственное его преимущество. Оно позволяет также повысить ясность кода, в особенности при применении >auto-переменных. Предположим, например, что у нас есть следующий исходный текст:

>auto result = findRecord( /* Аргументы */ );

>if (result == 0) {

> …

>}

Если вы случайно не знаете (или не можете быстро найти), какой тип возвращает >findRecord, может быть неясно, имеет ли >result тип указателя или целочисленный тип. В конце концов, значение >0 (с которым сравнивается >result) может быть в обоих случаях. С другой стороны, если вы увидите код

>auto result = findRecord( /* Аргументы */ );

>if (result == nullptr) {

> …

>}

то здесь нет никакой неоднозначности: result должен иметь тип указателя.

Особенно ярко сияет >nullptr, когда на сцене появляются шаблоны. Предположим, что у вас есть несколько функций, которые должны вызываться только при блокировке соответствующего мьютекса. Каждая функция получает указатель определенного вида:

>int f1(std::shared_ptr spw);    // Вызывается только при

>double f2(std::unique_ptr upw); // блокировке соответ-

>bool f3(Widget* pw);                    // ствующего мьютекса

Вызывающий код с передачей нулевых указателей может выглядеть следующим образом:

>std::mutex f1m, f2m, f3m; // Мьютексы для f1, f2 и f3


>using MuxGuard =          // C++11 typedef; см. раздел 3.3

> std::lock_guard;


>{

> MuxGuard g(f1m);           // Блокировка мьютекса для f1

> auto result = f1(0);       // Передача 0 функции f1

>}                           // Разблокирование мьютекса

>…

>{

> MuxGuard g(f2m);           // Блокировка мьютекса для f2

> auto result = f2(NULL);    // Передача NULL функции f2

>}                           // Разблокирование мьютекса

>…

>{

> MuxGuard g(f3m);           // Блокировка мьютекса для f3

> auto result = f3(nullptr); // Передача nullptr функции f3

>}                           // Разблокирование мьютекса

То, что в первых двух вызовах не был передан >nullptr, грустно; тем не менее код работает, а это чего-то да стоит. Однако повторяющиеся действия еще более грустны. Они просто беспокоят. Во избежание дублирования такого вида и предназначаются шаблоны, так что давайте превратим эти действия в шаблон.

>template

>         typename MuxType,

>         typename PtrType>

>auto lockAndCall(FuncType func,

>                 MuxType& mutex,

>                 PtrType ptr) -> decltype(func(ptr)) {

> using MuxGuard = std::lock_guard;


> MuxGuard g(mutex);

> return func(ptr);

>}

Если возвращаемый тип этой функции (>auto...->decltype(func(ptr)) заставляет вас чесать затылок, обратитесь к разделу 1.3, в котором объясняется происходящее. Там вы узнаете, что в С++ 14 возвращаемый тип можно свести к простому >decltype(auto):

>template

>         typename MuxType,

>         typename PtrType>

>decltype(auto) lockAndCall(FuncType func, // С++14

>                           MuxType& mutex,


Еще от автора Скотт Мейерс
Эффективное использование STL

В этой книге известный автор Скотт Мейерс раскрывает секреты настоящих мастеров, позволяющие добиться максимальной эффективности при работе с библиотекой STL.Во многих книгах описываются возможности STL, но только в этой рассказано о том, как работать с этой библиотекой. Каждый из 50 советов книги подкреплен анализом и убедительными примерами, поэтому читатель не только узнает, как решать ту или иную задачу, но и когда следует выбирать то или иное решение — и почему именно такое.


Как функции, не являющиеся методами, улучшают инкапсуляцию

Когда приходится инкапсулировать, то иногда лучше меньше, чем большеЯ начну со следующего утверждения: Если вы пишете функцию, которая может быть выполнена или как метод класса, или быть внешней по отношению к классу, Вы должны предпочесть ее реализацию без использования метода. Такое решение увеличивает инкапсуляцию класса. Когда Вы думаете об использовании инкапсуляции, Вы должны думать том, чтобы не использовать методы.Удивлены? Читайте дальше.


Рекомендуем почитать
Изучаем Java EE 7

Java Enterprise Edition (Java EE) остается одной из ведущих технологий и платформ на основе Java. Данная книга представляет собой логичное пошаговое руководство, в котором подробно описаны многие спецификации и эталонные реализации Java EE 7. Работа с ними продемонстрирована на практических примерах. В этом фундаментальном издании также используется новейшая версия инструмента GlassFish, предназначенного для развертывания и администрирования примеров кода. Книга написана ведущим специалистом по обработке запросов на спецификацию Java EE, членом наблюдательного совета организации Java Community Process (JCP)


Платформа J2Me

Эта книга научит вас, как разрабатывать программное обеспечение для платформы J2ME компании «Sun Microsystems». Эта книга придерживается стиля учебного пособия, это не справочное руководство.Цель — дать вам твердую основу в понятиях и техниках, которая даст вам возможность решиться на самостоятельную разработку качественных приложений.


Виртуальная библиотека Delphi

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


Обработка баз данных на Visual Basic.NET

Это практическое руководство разработчика программного обеспечения на Visual Basic .NET и ADO.NET, предназначенное для создания приложений баз данных на основе WinForms, Web-форм и Web-служб. В книге описываются практические способы решения задач доступа к данным, с которыми сталкиваются разработчики на Visual Basic .NET в своей повседневной деятельности. Книга начинается с основных сведений о создании баз данных, использовании языка структурированных запросов SQL и системы управления базами данных Microsoft SQL Server 2000.


Исчерпывающее руководство по написанию всплывающих подсказок

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


Программное обеспечение встроенных систем. Общие требования к разработке и документированию

Embedded system software. General requirements for development and documentationСтандарт подготовлен в развитие ГОСТ Р ИСО/МЭК 12207-99 «Информационная технология. Процессы жизненного цикла программных средств» с целью учета специфики разработки и документирования программного обеспечения встроенных систем реального времени.