С. Мейерс - Эффективный и современный C++ (1114942), страница 47
Текст из файла (страница 47)
emplace ( std : : forward<T> ( name) ) ;=Этот код прост, если вы понимаете механику, лежащую в основе выделенного параметра.Концептуально logAndAdd передает в функцию logAndAddimpl булево значение, указывающее, передан ли функции l ogAndAdd целочисленный тип, но значения t rue и f a l s e являются значениями времени вьтолнения, а нам для выбора верной версии logAndAddimp lнеобходимо разрешение перегрузки, т.е. явление времени компиляции. Это означает, чтонам нужен тип, соответствующий значению t rue, и другой тип, соответствующий значению f a l s e .
Такая необходимость - настолько распространенное явление, что стандартная библиотека предоставляет то, что нам нужно, под именами s t d : : t rue _t уреи std : : fa l s e_ type. Аргумент, передаваемый в logAndAddimpl функцией logAndAdd,является объектом типа, унаследованного от std : : t rue_type, если Тцелочисленныйтип, и от std : : fa l s e t ype, если Т таковым не является. Конечный результат заключается в том, что эта перегрузка logAndAddimpl является реальным кандидатом для вызовав logAndAdd, только если Т не является целочисленным типом.Вторая перегрузка охватывает противоположный случай, когда т представляет собойцелочисленный тип.
В этом случае l ogAndAddimpl просто ищет имя, соответствующеецелочисленному индексу, и передает это имя функции logAndAdd:-_std : : string nameFromidx ( int idx ) ;1 1 Как в разделе 5 . 41 1 Целочисленный аргумент : поиск имени и11 вызов с этим именем функции logAndAdd :5.5. Знакомство с альтернативами переrрузки дnя универсальных ссыпок1 93void logAndAddimpl ( int iclx , std: : true_type ){logAndAdd ( nameFromidx ( idx ) ) ;Наличие функции logAndAddimpl для поиска по индексу соответствующего имении передача его функции logAndAdd (откуда оно будет передано с помощью std : : forwardдрутой перегрузке функции l ogAndAddimpl) позволяет избежать размещения кода для записи в журнале в обеих перегрузках logAndAddimpl.В таком решении типы std : : t rue_type и std : : false_t ype являются "дескрипторами': единственная цель которых - обеспечить разрешение перегрузки требующимся намспособом.
Обратите внимание, что нам даже не нужны эти параметры. Они не служатникакой цели во время выполнения, и мы фактически надеемся, что компиляторы распознают, что параметры дескрипторов не используются, и соответствующим образомоптимизируют выполнимый образ программы. (Некоторые компиляторы так и поступают, по крайней мере иногда.) Вызов перегруженных функций реализации в logAndAdd"диспетчеризует" передачу работы правильной перегрузке путем создания нужного объекта дескриптора. Отсюда и название этого метода проектирования: диспетчеризация дескрипторов. Это стандартный строительный блок шаблонного метапрограммирования,и чем больше вы будете просматривать код внутри современных библиотек С++, темчаще вы будете с ним сталкиваться.Для наших целей важно не столько то, как работает диспетчеризация дескрипторов,сколько как она позволяет комбинировать универсальные ссылки и перегрузку без проблем, описанных в разделе 5.4.
Функция диспетчеризации - logAndAdd - принимает параметр, являющийся неограниченной универсальной ссылкой, но эта функция не перегружается. Перегружается функция реализации - logAndAddimpl, - которая принимаетпараметр, представляющий собой универсальную ссылку, но разрешение вызова этойфункции зависит не только от параметра универсальной ссылки, но и от параметра дескриптора, а значения дескрипторов спроектированы таким образом, чтобы было не более одной совпадающей перегрузки. В результате то, какая из перегрузок будет вызвана,определяется дескриптором.
Тот факт, что параметр, представляющий собой универсальную ссылку, всегда генерирует точное соответствие своему аргументу, значения не имеет.Оr р аничен ия шабл о нов, получающих уни ве рсальные ссылкиКлючевым моментом диспетчеризации дескрипторов является существование (неперегруженной) функции в качестве клиентского API. Эта единственная функция распределяет работу между функциями реализации. Обычно создать такую неперегруженнуюфункцию диспетчеризации несложно, но второй пример, рассмотренный в разделе 5.4,в котором рассматривался конструктор класса Person с прямой передачей, является исключением.
Компиляторы могут самостоятельно генерировать копирующие и перемещающие конструкторы, так что, если даже мы напишем один конструктор и используемв нем диспетчеризацию дескрипторов, некоторые вызовы конструкторов могут быть1 94Глава 5. Rvаluе-ссылки, семантика перемещений и прямая передачаобработаны сгенерированными компиляторами функциями, которые обходят системудиспетчеризации дескрипторов.По правде говоря, реальная проблема не в том, что генерируемые компиляторамифункции иногда обходят диспетчеризацию дескрипторов; на самом деле она в том, чтоони не всегда ее обходят. Вы практически всегда хотите, чтобы копирующий конструкторкласса обрабатывал запрос на копирование lvalue этого типа, но, как показано в разделе 5.4, предоставление конструктора, принимающего универсальную ссылку, приводитк тому, что при копировании неконстантных lvalue вызывается конструктор с универсальной ссылкой, а не копирующий конструктор.
В этом разделе также поясняется, чтокогда базовый класс объявляет конструктор с прямой передачей, именно этот конструктор обычно вызывается при традиционной реализации производным классом копирующего и перемещающего конструкторов, несмотря на то что корректным поведениемявляется вызов копирующих и перемещающих конструкторов.Для подобных ситуаций, в которых перегруженная функция, принимающая универсальную ссылку, оказывается более "жадной", чем вы хотели, но недостаточно жадной,чтобы действовать как единственная функция диспетчеризации, метод диспетчеризациидескрипторов оказывается не тем, что требуется. Вам нужна другая технология, и эта технологияstd : : еnаЫе i f .s t d : : е n аЫ е i f дает вам возможность заставить компиляторы вести себя так,как если бы определенного шаблона не существовало.
Такие шаблоны называют отключенными (disaЫed). По умолчанию все шаблоны включены, но шаблон, использующий s t d : : enaЫe _ i f , включен, только если удовлетворяется условие, определенноеstd : : enaЫe_ i f . В нашем случае мы хотели бы включить конструктор Pe rson с прямойпередачей, только если передаваемый тип не является Person. Если переданный типPe rson, то мы хотели бы отключить конструктор с прямой передачей (т.е.
заставить компилятор его игнорировать), поскольку при этом для обработки вызова будет примененкопирующий или перемещающий конструктор, а это именно то, чего мы хотим, когдаодин объект типа Pe rson инициализируется другим объектом того же типа.Способ выражения этой идеи не слишком сложен, но имеет отталкивающий синтаксис, в особенности если вы не встречались с ним ранее. Имеются некоторые шаблоны, располагающиеся вокруг части условия s t d : : еnаЫе _i f, так •по начнем с него. Вотобъявление конструктора с прямой передачей класса P e rson, который показывает неболее чем необходимо для простого использования std : : еnаЫе_ i f. Я покажу толькообъявление этого конструктора, поскольку применение std : : еnаЫе_ i f не влияет на реализацию функции.
Реализация остается той же, что и в разделе 5.4:--class Person {puЬlic :template<typename Т,typen.ame=typename std: : еnаЫе if<усповяе> : : type>_explicit Person ( T & & n ) ;};S.S. Знакомство с альтернативами перегрузки для универсальных ссылок1 95Вынужден с прискорбием сообщить, что для того, чтобы разобраться, что происходит в выделенном тексте, вам следует проконсультироваться с друтими источниками информации, так как в этой книге у меня просто нет места, чтобы подробно все описать.(В процессе вашего поиска поищите как s t d : : еnаЫе i f, так и волшебную аббревиатуру"SFINAE", поскольку именно эта технология позволяет работать std : : еnаЫе_ i f.) Здесья хочу сосредоточиться на выражении условия, которое управляет тем, является ли конструктор включенным.Условие, которое мы хотим указать, - что тип Т не является Person, т.е.
что шаблонизированный конструктор может быть включенным, только если т является типом, отличным от Person. Благодаря свойствам шаблонов мы можем определить, являются ли дватипа одним и тем же ( s t d : : is _ same ) , так что создается впечатление, что интересующеенас условие можно записать как ! std : : i s same < Person , Т> : : value. (Обратите вниманиена символ " ! " в начале выражения. Мы хотим, чтобы типы Person и Т не совпадали.)Это близко к тому, что нам надо, но не совсем верно, поскольку, как поясняет раздел 5.6,тип, выведенный для универсальной ссылки, инициализированной lvalue, всегда является lvаluе-ссылкой. Это означает, что в коде наподобие_Person p ( "Nancy" ) ;auto cloneOfP ( p ) ;11 Инициализацияспомощью lvalueтип Т в универсальном конструкторе будет выведен как P e r s o n & .
Типы P e r s o nи P e r s o n & - разные, и результат s t d : : i s _same отражает этот факт: значениеstd : : is _ same < Person , Person& > : : value ложно.Если разобраться, что означает, что шаблонный конструктор в классе Person долженбыть включен только тогда, когда Т не является Pers on, то мы поймем, что, глядя на Т,мы хотим игнорировать следующее.•Ссылки. С точки зрения определения, должен ли быть включен конструктор с универсальной ссылкой, типы Person, Person& и Perso n & & должны рассматриватьсякак идентичные типу Person.•Модификаторы const и volatile.
С той же точки зрения типы cons t Person,vola t i l e Person и const vo lat i l e Person должны рассматриваться как идентичные типу Person.Это означает, что нам нужен способ удалить все ссылки, const и volat i l e из типа Тперед тем как выяснять, совпадает ли он с типом Person.