Д. Вандевурд, Н.М. Джосаттис - Шаблоны C++. Справочник разработчика (2003) (1160769), страница 78
Текст из файла (страница 78)
раздел )3.3, р. гЗЗ). Глава 21. Кортежи 434 гесигп тир1е<т1,т2,тЗ,т4,т5>(а1,а2,аЗ,а4,а5); Ниже приведена программа, иллюстрирующая применение объектов класса Тир1е. // сир1ев/сир1е1.срр 41пс1ис)е "сир1е1.Ьрр" ()1пс1ис)е <йовсгеагв> йпс ша1п() ( // Создание и использование объекта класса Тир1е, // содержашего только одно поле Тир1Е<1ПС> с1; ча1<1>(С1) += 42; вМ::соип «а1.ч1() «вЫ::еп61> // Создание и использование дуэта Тир1е<Ьоо1,1пс> С2; все)::соис «ча1<1>(с2] « вес)::соип «С2.ч1() «вес)::епг)1) // Создание и использование трио Тир1е<Ьоо1,1пс,с)оиЬ1е> сЗ; ча1<1>(СЗ) = Сгие; ча1<2>(сЗ) = 42; ча1<З>(сЗ) = 0.2г всй::соис « ча1<1>(сЗ) « вМ::соис «ча1<2>(ЬЗ) « всб::соис «ча1<3>(сЗ) «вМ::епс)1; сЗ = па)се сир1е(йа1ве, 23, 13.13) вес)::соип « ча1<1>(СЗ) « вес)::соис « ча1<2>(сЗ) « впй::соие «ча1<3>(СЗ) «вМ::еп611 куренное Т4, Сурепаше Т5> йп11пе Тир1е<Т1,Т2,ТЗ,Т4,Т5> та)се сир1е(Т1 Т2 ТЗ Т4 Т5 сопвс йа1, сопев йа2, сопле йаЗ, сопев йа4, сопвс йа5) 21.4.
Заключение 435 // Создание и использование квартета Тпр1е<Ьоо1,1па,й1оае,доиЬ1е> С4(акпе,42,13,1.95583)! ваб: >попа « ча1<4>(С4) « вМ::епй1! зсс1::соус «с4.ч2() .ч2() л2() «вес)::епс!1; ) В реализациях, предназначенных для промышленного применения, представленный ранее код класса Тпр1е следовало бы дополнить. Например, можно было бы задать шаблоны оператора присвоения, облегчаклцие преобразование объектов этого класса. Если этого не сделать, то типы соответствующих полей должны точно соответствовать друг другу.
Тир1е<Ьоо1,1пе,й1оае> СЗ! с3 = гаа)се сир1е(йа1ве, 23, 13.13); // ОШИБКА: значение 13.13 // имеет тип с)оиЬ1е 21.4. Заключение Разработка кортежей — одна из областей применения шаблонов, в которой многие программисты работают независимо друг от друга. Особенности создаваемых ими версий очень различаются, однако в основе многих реашоаций лежит идея структуры рекурсивных дуэтов (подобных рассмотренным в данной главе).
Один из интересных подходов предложен Андреем Александреску (Аш)ге! А!ехапдгеясп) [Ц. Он четко отделил список типов от списка полей кортежа. В результате вводится понятие списка типов ((уре 1!аг)„которое находит самые разные применения (одно из них — конструирование кортежей с инкапсулированными типами). В разделе 13.13, стр. 248, рассматривается понятие списочного параметра, представляющего собой расширение языка, с помощью которопз реализация кортежей почти тривиальна.
Глава 22 Обьекты-функции и обратные вызовы Объект-фуикиия (йпсноп об) ес1), также называемый функтором (йшс1ог), представляет собой любой объект, который можно вызывать с помощью синтаксиса вызова функции. В языке программирования С к такому синтаксису приводит использование конструкций трех видов: функций, функциеобразных макросов и указателей на функции.
Поскольку функции и макросы — это не обьекты; получается, что единственными функторами, доступными в С, являются указатели на функции. Язык С++ предоставляет больше возможностей. Оператор вызова функции может быть перегружен для классов, существует концепция ссылки на функцию, а кроме того, синтаксис вызова функции имеют функции-члены и указатели на них.
Нельзя сказать, что все эти концепции одинаково полезны, однако сочетание понятия функтора с параметрнзацией времени компиляции, возможной благодаря шаблонам, приводит к новым мощным методам программирования. Кроме разработки типов функторов, в этой главе большое внимание уделяется также идиомам их использования. При работе с функторами почти неизбежно приходится иметь дело с обраглными вызовами (са11Ьаск) — ситуацией, когда пользователю библиотеки нужно, чтобы библиотечная функция вызвала некоторую функцию, определенную в коде пользователя. В качестве классического примера можно привести функцию сортировки, для работы которой нужна функция, способная сравнивать два элемента из сортируемого набора данных. В этом случае функция сравнения передается в функцию сортиРовки в качестве функтора. Традиционно термин обратный вызов применяется лля функторов, которые передаются в аргументах вызова функций (в противоположносп„ например, аргументам шаблонов), и мы будем придерживаться этой традиции.
К сожалению, термины объект-функция (бзпс1юп оЪ)ес1) и функтор (бзпс1ог) несколько неоднозначны в том смысле, что разные члены сообщества программистов на С++ иногда трактуют их немного по-разному. Чаще всего различия в определениях касаются того, причислять ли к функторам (или объектам-функциям) только объекты классов, или указатели на функции также являются функторами. Кроме того, нередко приходится читать или слышать, как в ходе обсуждения шаны классов объектов-функций называют объектами-фУнкциями. Другими словами, фраза "класс объектов-функций то-то и то-то..." сокращается до "объекты-функции то-то и то-то...".
Несмотря на то что в по- Глава 22. Объекты-функции и обратные вызовы 438 вседневной работе эта терминология применяется несколько небрежно, она положена в основу данной главы, причем в том виде, в котором определена в ее начале. Прежде чем углубиться в особенности реализации функторов с помощью шаблонов, обсудим некоторые свойства вызовов функций, которыми объясняются определенные преимущества функгоров, основанных на шаблонах. 22.1. Прямые, непрямые и встраиваемые вызовы Обычно, наталкиваясь на определение невстраиваемой функции, компилятор С или С++ генерирует ее код, который заносится в объектный файл.
Кроме того, он создает имя, связанное с этим машинным кодом. В языке С зто имя, как правило, совпадает с именем самой функции, а в С++ оно обычно дополняется закодированными типами параметров, благодаря чему обеспечиваются уникальные имена перегруженных функций (полученное в результате имя часто называют скорректированньии лыеием (шапй(ео пате), иногда также применяется термин декорированное имя (десогагес! пате)).
Аналогично, когда компилятору встречается вызов функции, например б(); он генерирует машинный код для вызова функции данного типа В большинстве языков программирования для выполнения команды вызова необходим начальный адрес вызываемой подпрограммы. Этот адрес может быть составной частью инструкции (в таком случае инструкция называется п)эямьий вызовом (ейгесс са11)) либо храниться где-то в памяти нли в аппаратном регистре (непрямой, или косвенный вызов (!псйгес! сай)). Почти во всех современных архитектурах вычислительных систем в наличии имеются инструкции вызова подпрограмм обоих типов. По причинам, описание которых выходит за рамки настоящей книги, прямые вызовы обрабатываются эффективнее, чем косвенные. Фактически по мере усложнения архитектуры компьютеров разница в производительности этих двух видов вызовов возрастает, поэтому компиляторы по возможности пытаются сгенерировать инструкцию прямого вызова.
В общем случае компилятору неизвестно, по какому адресу расположена функция (например, она может находиться в другой единице трансляции). Однако если компилятору известно нмя функции, он генерирует инструкцию прямого вызова с фиктивным адресом. Кроме того, в объектном файле с телом функции генерируется входная точка, благодаря которой компоновщик получает возможность заменить инструкцию вызова с фиктивным адресом инструкцией, в которую подставлен адрес функции с указанным именем. Поскольку компоновщик имеет доступ ко всем единицам трансляции, он располагает информацией о расположении инструкций вызова и обьектных файлов функций и поэтому всегда способен организовать прямые вызовы .
1 1 Анелогичщло роль компоновщик играет, например, лри доступе к переменным, заданным е определенных пространствах имен. 22.1. Прямые, непрямые и встраиваемые вызовы 439 К сожалению, если имя функции неизвестно, приходится применять непрямые вызовы. Обычно это бывает тогда, когда функции вызываются с помощью указателей. чоЫ йоо(чоЫ (*рХ) () ) ( рб(); // Непрямой вызов с помощью // указателя на функцию рб В приведенном примере компилятор не может знать, на какую функцию указывает параметр рб (в другом месте функция боо ( ) может быть вызвана с другим указателем в качестве аргумента), поэтому компоновщик не может ориентироваться на имена Адрес вызова остается неизвестным до непосредственного выполнения кода функции. Несмотря на то что на современных компьютерах инструкции прямого вызова часто выполняются почти так же быстро, как и любые другие (например, инструкции сложения двух целых чисел), они все же могут серьезно снизить производительность работы программы.