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

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

. (Обратите внимание на символ “>!” в начале выражения. Мы хотим, чтобы типы >Person и >T не совпадали.) Это близко к тому, что нам надо, но не совсем верно, поскольку, как поясняет раздел 5.6, тип, выведенный для универсальной ссылки, инициализированной lvalue, всегда является lvalue-ссылкой. Это означает, что в коде наподобие

>Person p("Nancy");

>auto cloneOfP(p); // Инициализация с помощью lvalue

тип >T в универсальном конструкторе будет выведен как >Person&. Типы >Person и >Person& — разные, и результат >std::is_same отражает этот факт: значение >std::is_same::value ложно.

Если разобраться, что означает, что шаблонный конструктор в классе >Person должен быть включен только тогда, когда >T не является >Person, то мы поймем, что, глядя на >T, мы хотим игнорировать следующее.

• Ссылки. С точки зрения определения, должен ли быть включен конструктор с универсальной ссылкой, типы >Person, >Person& и >Person&& должны рассматриваться как идентичные типу >Person.

• Модификаторы>constи>volatile. С той же точки зрения типы >const Person, >volatile Person и >const volatile Person должны рассматриваться как идентичные типу >Person.

Это означает, что нам нужен способ удалить все ссылки, >const и >volatile из типа >T перед тем как выяснять, совпадает ли он с типом >Person. И вновь на выручку приходит стандартная библиотека, предоставляя шаблон >std::decay. Тип >std::decay::type представляет собой то же самое, что и тип >T, но из него удалены все ссылки и квалификаторы >const и >volatile. (Я немного вас обманул, потому что >std::decay, кроме того, превращает массивы и типы функций в указатели (см. раздел 1.1), но для наших целей можно считать, что >std::decay ведет себя так, как я описал.) Условие, которое должно выполняться для включения рассматриваемого конструктора, имеет вид

>!std::is_same::type>::value

т.e. >Person не совпадает с типом >T, без учета всех ссылок и квалификаторов >const и >volatile. (Как поясняется в разделе 3.3, ключевое слово typename перед >std::decay необходимо, поскольку тип >std::decay::type зависит от параметра шаблона >T.)

Вставка этого условия в шаблон >std::enable_if выше, а также форматирование результата для того, чтобы проще понять взаимоотношения между частями кода, дает следующее объявление конструктора с прямой передачей класса >Person:

>class Person {

>public:

> template<

>  typename T,

>  typename = typename std::enable_if<

>   !std::is_same

>                 typename std::decay::type

>                >::value

>  >::type

> >

> explicit Person(T&& n);

>};

Если вы никогда ранее не видели ничего подобного, не пугайтесь. Есть причина, по которой я оставил этот метод напоследок. Если для того, чтобы избежать смешивания универсальных ссылок и перегрузки вы можете использовать один из прочих методов (а это почти всегда возможно), вы должны это сделать. Тем не менее, если вы привыкнете к функциональному синтаксису и множеству угловых скобок, это не так плохо. Кроме того, это позволяет получить поведение, к которому вы стремитесь. С учетом приведенного выше объявления построение объекта >Person из другого объекта >Person (lvalue или rvalue, с квалификатором >const или без него, с квалификатором >volatile или без него) никогда не вызовет конструктор, принимающий универсальную ссылку.

Мы добились успеха? Дело сделано?

Пока что нет. Не спешите праздновать. Раздел 5.4 все еще посылает нам свои приветы. Нам надо заткнуть этот фонтан окончательно.

Предположим, что класс, производный от >Person, реализует операции копирования и перемещения традиционным способом:

>class SpecialPerson: public Person {

>public:

> SpecialPerson (const SpecialPerson& rhs) // Копирующий

> : Person(rhs)            // конструктор; вызывает конструктор

> { … }                    // базового класса с прямой передачей!


> SpecialPerson(SpecialPerson&& rhs)      // Перемещающий

> : Person(std::move(rhs)) // конструктор; вызывает конструктор

> { … }                    // базового класса с прямой передачей!

>};

Это тот же код, который вы видели ранее, в конце предыдущего раздела, включая комментарии, увы, оставшиеся справедливыми. Копируя или перемещая объект >SpecialPerson, мы ожидаем, что части базового класса будут скопированы или перемещены с помощью копирующего или, соответственно, перемещающего конструктора базового класса. Однако в этих функциях мы передаем объекты >SpecialPerson конструкторам базового класса, а поскольку >SpecialPerson не совпадает с >Person (даже после применения >std::decay), конструктор с универсальной ссылкой в базовом классе оказывается включенным и без проблем проходит проверку на идеальное совпадение с аргументом >SpecialPerson. Это точное соответствие лучше преобразования производного класса в базовый, необходимого для связывания объекта >SpecialPerson с параметром >Person в копирующем и перемещающем конструкторах класса >Person, так что при имеющемся коде копирование и перемещение объектов >SpecialPerson будет использовать для копирования и перемещения частей базового класса конструктор с универсальной ссылкой класса >Person! Это чудное ощущение дежавю раздела 5.4…


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

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


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

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


Рекомендуем почитать
Pro Git

Разработчику часто требуется много сторонних инструментов, чтобы создавать и поддерживать проект. Система Git — один из таких инструментов и используется для контроля промежуточных версий вашего приложения, позволяя вам исправлять ошибки, откатывать к старой версии, разрабатывать проект в команде и сливать его потом. В книге вы узнаете об основах работы с Git: установка, ключевые команды, gitHub и многое другое.В книге рассматриваются следующие темы:основы Git;ветвление в Git;Git на сервере;распределённый Git;GitHub;инструменты Git;настройка Git;Git и другие системы контроля версий.


Java 7

Рассмотрено все необходимое для разработки, компиляции, отладки и запуска приложений Java. Изложены практические приемы использования как традиционных, так и новейших конструкций объектно-ориентированного языка Java, графической библиотеки классов Swing, расширенной библиотеки Java 2D, работа со звуком, печать, способы русификации программ. Приведено полное описание нововведений Java SE 7: двоичная запись чисел, строковые варианты разветвлений, "ромбовидный оператор", NIO2, новые средства многопоточности и др.


MFC и OpenGL

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


Симуляция частичной специализации

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


Обработка событий в С++

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


Питон — модули, пакеты, классы, экземпляры

Python - объектно-ориентированный язык сверхвысокого уровня. Python, в отличии от Java, не требует исключительно объектной ориентированности, но классы в Python так просто изучить и так удобно использовать, что даже новые и неискушенные пользователи быстро переходят на ОО-подход.