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

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

— принимает параметр, являющийся неограниченной универсальной ссылкой, но эта функция не перегружается. Перегружается функция реализации — >logAndAddImpl, — которая принимает параметр, представляющий собой универсальную ссылку, но разрешение вызова этой функции зависит не только от параметра универсальной ссылки, но и от параметра дескриптора, а значения дескрипторов спроектированы таким образом, чтобы было не более одной совпадающей перегрузки. В результате то, какая из перегрузок будет вызвана, определяется дескриптором. Тот факт, что параметр, представляющий собой универсальную ссылку, всегда генерирует точное соответствие своему аргументу, значения не имеет.

Ограничения шаблонов, получающих универсальные ссылки

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

По правде говоря, реальная проблема не в том, что генерируемые компиляторами функции иногда обходят диспетчеризацию дескрипторов; на самом деле она в том, что они не всегда ее обходят. Вы практически всегда хотите, чтобы копирующий конструктор класса обрабатывал запрос на копирование lvalue этого типа, но, как показано в разделе 5.4, предоставление конструктора, принимающего универсальную ссылку, приводит к тому, что при копировании неконстантных lvalue вызывается конструктор с универсальной ссылкой, а не копирующий конструктор. В этом разделе также поясняется, что когда базовый класс объявляет конструктор с прямой передачей, именно этот конструктор обычно вызывается при традиционной реализации производным классом копирующего и перемещающего конструкторов, несмотря на то что корректным поведением является вызов копирующих и перемещающих конструкторов.

Для подобных ситуаций, в которых перегруженная функция, принимающая универсальную ссылку, оказывается более “жадной”, чем вы хотели, но недостаточно жадной, чтобы действовать как единственная функция диспетчеризации, метод диспетчеризации дескрипторов оказывается не тем, что требуется. Вам нужна другая технология, и эта технология — >std::enable_if.

>std::enable_if дает вам возможность заставить компиляторы вести себя так, как если бы определенного шаблона не существовало. Такие шаблоны называют отключенными (disabled). По умолчанию все шаблоны включены, но шаблон, использующий >std::enable_if, включен, только если удовлетворяется условие, определенное >std::enable_if. В нашем случае мы хотели бы включить конструктор >Person с прямой передачей, только если передаваемый тип не является >Person. Если переданный тип — >Person, то мы хотели бы отключить конструктор с прямой передачей (т.e. заставить компилятор его игнорировать), поскольку при этом для обработки вызова будет применен копирующий или перемещающий конструктор, а это именно то, чего мы хотим, когда один объект типа >Person инициализируется другим объектом того же типа.

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

>class Person {

>public:

> template

>  typename = typename std::enable_if<условие>::type>

> explicit Person(T&& n);

> …

>};

Вынужден с прискорбием сообщить, что для того, чтобы разобраться, что происходит в выделенном тексте, вам следует проконсультироваться с другими источниками информации, так как в этой книге у меня просто нет места, чтобы подробно все описать. (В процессе вашего поиска поищите как >std::enable_if, так и волшебную аббревиатуру “SFINAE”, поскольку именно эта технология позволяет работать >std::enable_if.) Здесь я хочу сосредоточиться на выражении условия, которое управляет тем, является ли конструктор включенным.

Условие, которое мы хотим указать, — что тип >T не является >Person, т.e. что шаблонизированный конструктор может быть включенным, только если >T является типом, отличным от >Person. Благодаря свойствам шаблонов мы можем определить, являются ли два типа одним и тем же (>std::is_same), так что создается впечатление, что интересующее нас условие можно записать как >!std::is_same::value


Еще от автора Скотт Мейерс
Эффективное использование 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 так просто изучить и так удобно использовать, что даже новые и неискушенные пользователи быстро переходят на ОО-подход.