Совет 41. Разберитесь, для чего нужны ptr_fun, mem_fun и mem_fun_ref
Совет 41. Разберитесь, для чего нужны ptr_fun, mem_fun и mem_fun_ref
Загадочные функции ptr_fun/mem_fun/mem_fun_ref часто вызывают недоумение. В одних случаях их присутствие обязательно, в других они не нужны... но что же они все-таки делают? На первый взгляд кажется, что они бессмысленно загромождают имена функций. Их неудобно вводить и читать, они затрудняют понимание программы. Что это — очередные пережитки прошлого STL (другие примеры приводились в советах 10 и 18) или синтаксическая шутка, придуманная членами Комитета по стандартизации с извращенным чувством юмора?
Действительно, имена выглядят довольно странно, но функции ptr_fun, mem_fun и mem_fun_ref выполняют важные задачи. Если уж речь зашла о синтаксических странностях, надо сказать, что одна из важнейших задач этих функций связана с преодолением синтаксической непоследовательности С++.
В С++ существуют три варианта синтаксиса вызова функции f для объекта х:
f(x); // Синтаксис 1: f не является функцией класса
//(вызов внешней функции)
x.f(); // Синтаксис 2: f является функцией класса, а х
// является объектом или ссылкой на объект
p->f(); // Синтаксис 3: f является функцией класса,
// а р содержит указатель на х
Рассмотрим гипотетическую функцию, предназначенную для «проверки» объектов Widget:
void test(Widget& w): // Проверить объект w. Если объект не проходит
// проверку, он помечается как "плохой"
Допустим, у нас имеется контейнер объектов Widget:
vector<Widget> vw;// vw содержит объекты Widget
Для проверки всех объектов Widget в контейнере vw можно воспользоваться алгоритмом for_each:
for_each(vw.begin(),vw.end(),test): // Вариант 1 (нормально компилируется)
Но представьте, что test является функцией класса Widget, а не внешней функцией (то есть класс Widget сам обеспечивает проверку своих объектов):
class Widget { public:
void test();// Выполнить самопроверку. Если проверка
// завершается неудачей, объект помечается
};// как "плохой"
В идеальном мире мы могли бы воспользоваться for_each для вызова функции Widget::test всех объектов вектора vw:
for_each(vw.begin(),vw.end(),
SWidget::test);// Вариант 2 (не компилируется!)
Более того, если бы наш мир был действительно идеальным, алгоритм for_each мог бы использоваться и для вызова Widget::test в контейнере указателей Widget*:
list<Widget*> lpw:// Список lpw содержит указатели
// на объекты Widget
for_each(lpw.begin(),lpw.end(),
// Вариант 3 (не компилируется!) Swidget::test);
Но подумайте, что должно было бы происходить в этом идеальном мире. Внутри функции for_each в варианте 1 вызывается внешняя функция, поэтому должен использоваться синтаксис 1. Внутри вызова for_each в варианте 2 следовало бы использовать синтаксис 2, поскольку вызывается функция класса. А внутри функции foreach в варианте 3 пришлось бы использовать синтаксис 3, поскольку речь идет о функции класса и указателе на объект. Таким образом, нам понадобились бы три разных версии for_each — разве такой мир можно назвать идеальным?
В реальном мире существует только одна версия for_each. Нетрудно представить себе возможную ее реализацию:
template<typename InputIterator.typename Function>
Function for_each(InputIterator begin. InputIterator end, Function f)
{
while (begin!=end) f(*begin++);
}
Жирный шрифт используется для выделения того, что при вызове foreach используется синтаксис 1. В STL существует всеобщее правило, согласно которому функции и объекты функций всегда вызываются в первой синтаксической форме (как внешние функции). Становится понятно, почему вариант 1 компилируется, а варианты 2 и 3 не компилируются — алгоритмы STL (в том числе и for_each) жестко закодированы на использование синтаксиса внешних функций, с которым совместим только вариант 1.
Теперь понятно, для чего нужны функции mem_fun и mem_fun_ref. Они обеспечивают возможность вызова функций классов (обычно вызываемых в синтаксисе 2 и 3) при помощи синтаксиса 1.
Принцип работы mem_fun и mem_fun_ref прост, хотя для пущей ясности желательно рассмотреть объявление одной из этих функций. В действительности они представляют собой шаблоны функций, причем существует несколько вариантов mem_fun и mem_fun_ref для разного количества параметров и наличия-отсутствия константности адаптируемых ими функций классов. Одного объявления вполне достаточно, чтобы разобраться в происходящем:
template<typename R, typename C> // Объявление mem_fun для неконстантных
mem_fun_t<R.C>// функций без параметров. С - класс.
mem_fun(R(C::*pmf)0);// R - тип возвращаемого значения функции.
// на которую ссылается указатель
Функция mem_fun создает указатель pmf на функцию класса и возвращает объект типа mem_fun_t. Тип представляет собой класс функтора, содержащий указатель на функцию и функцию operator(), которая по указателю вызывает функцию для объекта, переданного operator(). Например, в следующем фрагменте:
list<Widget*> lpw;
// См. ранее
for_each(lpw.begin(), lpw.end(),
mem_fun(&Widget::test)); // Теперь нормально компилируется
При вызове for_each передается объект типа mem_fun_t, содержащий указатель на Widget:: test. Для каждого указателя Widget* в lpw алгоритм for_each «вызывает» объект mem_fun_t с использованием синтаксиса 1, а этот объект непосредственно вызывает Widget::test для указателя Widget* с использованием синтаксиса 3.
В целом mem_fun приводит синтаксис 3, необходимый для Widget::test при использовании с указателем Widget*, к синтаксису 1, используемому алгоритмом for_ each. По вполне понятным причинам такие классы, как mem_fun_t, называются адаптерами объектов функций. Наверное, вы уже догадались, что по аналогии со всем, о чем говорилось ранее, функции mem_fun_def адаптируют синтаксис 2 к синтаксису 1 и генерируют адаптеры типа mem_fun_left.
Объекты, создаваемые функциями mem_fun и mem_fun_ref, не ограничиваются простой унификацией синтаксиса для компонентов STL. Они (а также объекты, создаваемые функцией ptr_fun) также предоставляют важные определения типов. Об этих определениях уже было рассказано в совете 40, поэтому я не стану повторяться. Тем не менее, стоит разобраться, почему конструкция
for_each(vw.begin(),vw.end(),test): // См. ранее, вариант 1.
// Нормально компилируется
компилируется, а следующие конструкции не компилируются:
for_each(vw.begin().vw.end(),&Widget::test); //См. ранее, вариант 2.
// Не компилируется.
for_each(lpw.begin(),lpw.end(), &Widget::test): //См. ранее, вариант 3.
//Не компилируется
При первом вызове (вариант 1) передается настоящая функция, поэтому адаптация синтаксиса вызова для for_each не нужна; алгоритм сам вызовет ее с правильным синтаксисом. Более того, foreach не использует определения типов, добавляемые функцией ptr_fun, поэтому при передаче test функция ptr_fun не нужна. С другой стороны, добавленные определения не повредят, поэтому следующий фрагмент функционально эквивалентен приведенному выше:
for_each(vw.begin(),vw.end().ptr_fun(test)): // Компилируется и работает.
// как вариант 1.
Если вы забываете, когда функция ptr_fun обязательна, а в каких случаях без нее можно обойтись, лучше используйте ее при всех передачах функций компонентам STL. STL игнорирует лишние вызовы, и они не отражаются на быстродействии программы. Возможно, во время чтения вашей программы кто-нибудь удивленно поднимет брови при виде лишнего вызова ptr_fun. Насколько это беспокоит вас? Наверное, ответ зависит от природной мнительности.
Существует и другой подход — использовать ptr_fun в случае крайней необходимости. Если функция отсутствует там, где необходимы определения типов, компилятор выдает сообщение об ошибке. Тогда вы возвращаетесь к программе и включаете в нее пропущенный вызов.
С mem_fun и mem_fun_ref ситуация принципиально иная. Эти функции всегда должны применяться при передаче функции компонентам STL, поскольку помимо определения типов (необходимых или нет) они адаптируют синтаксис вызова, который обычно используется для функций класса, к синтаксису, принятому в STL. Если не использовать эти функции при передаче указателей на функции класса, программа не будет компилироваться.
Остается лишь разобраться со странными именами адаптеров. Перед нами самый настоящий пережиток прошлого STL. Когда впервые возникла необходимость в адаптерах, разработчики STL ориентировались на контейнеры указателей (с учетом недостатков таких контейнеров, описанных в советах 7,20 и 33, это может показаться странным, но не стоит забывать, что контейнеры указателей поддерживают полиморфизм, а контейнеры объектов — нет). Когда понадобился адаптер для функций классов (MEMber FUNctions), его назвали mem_fun. Только позднее разработчики поняли, что для контейнеров объектов понадобится другой адаптер, и для этой цели изобрели имя mem_fun_ref. Конечно, выглядит не слишком элегантно, но... бывает, ничего не поделаешь. Пусть тот, кому никогда не приходилось жалеть о поспешном выборе имен своих компонентов, первым бросит камень.
Более 800 000 книг и аудиокниг! 📚
Получи 2 месяца Литрес Подписки в подарок и наслаждайся неограниченным чтением
ПОЛУЧИТЬ ПОДАРОКЧитайте также
Предотвращение перегруженности: для чего нужны несколько потоков
Предотвращение перегруженности: для чего нужны несколько потоков Один из главных недостатков решения на основе демона bdflush состоит в том, что демон bdflush имел всего один поток выполнения. Это приводило к возможности зависания демона при большом количестве операций
Вам нужны цели?
Вам нужны цели? Если вы любите ставить цели и достигать их, то можете играть и так. В Minecraft предусмотрено около тридцати достижений, которые можно использовать как квесты. Достигнув одной цели, вы можете двигаться дальше. Все это поможет вам развить навыки выживания
1.6. Какие сервера бывают и для чего они нужны
1.6. Какие сервера бывают и для чего они нужны Для полноты картины рассмотрим несколько различных типов серверов:1. Сервер локальной сети.2. Шлюз.3. Сервер удаленного доступа.Сервер любого типа вы сможете настроить с помощью данной
1.7.1. Для чего нужны каталоги
1.7.1. Для чего нужны каталоги Переходя из категории в категорию, пользователи находят нужную им информацию. Чтобы людям было проще выбрать, на какой сайт перейти, каждый ресурс сопровождается коротким или подробным описанием.Работа каталогов с новыми ресурсами построена
Зачем нужны оптимизаторы?
Зачем нужны оптимизаторы? Описанная в предыдущих главах схема охватывает практически все процессы создания и наполнения портала, начиная с составления семантического ядра и заканчивая размещением готовых текстов. При этом до сих пор у нас практически не было работы для
Для чего нужны индексы?
Для чего нужны индексы? Единственное, чему способствуют индексы, - это ускорению поиска записи по ее индексированному полю (индексированное - значит входящее в индекс).Итак, основная функция индексов - обеспечивать быстрый поиск записи в таблице. Любое использование
Обобщение mem_fun
Обобщение mem_fun Проблемы с интерфейсом mem_fun_t Для начала обратим внимание на то, что mem_fun_t::operator() принимает только указатель на объект класса, чьим членом является функция pm. От этого было бы неплохо избавиться. Рассмотрим такой вариант:template<class TT, class R, class T> struct gen_mem_fun_t
Правило 41: Разберитесь в том, что такое неявные интерфейсы и полиморфизм на этапе компиляции
Правило 41: Разберитесь в том, что такое неявные интерфейсы и полиморфизм на этапе компиляции В мире объектно-ориентированного программирования преобладают явные интерфейсы и полиморфизм на этапе исполнения. Например, рассмотрим следующий (бессмысленный) класс:class Widget
Правило 49: Разберитесь в поведении обработчика new
Правило 49: Разберитесь в поведении обработчика new Когда оператор new не может удовлетворить запрос на выделение памяти, он возбуждает исключение. Когда-то он возвращал нулевой указатель, и некоторые старые компиляторы все еще так и поступают. Вы можете столкнуться с таким
Разберитесь с долгами
Разберитесь с долгами Расплачивайтесь по долгам вашего кода и дизайнаМы обычно говорим о долгах в денежном выражении, но долги могут принимать и другие формы. Вы можете быстро обрасти долгами кода и дизайна.Наваяли блок кода, который функционален, но все еще неопрятен —
Зачем нужны ярлыки?
Зачем нужны ярлыки? Особый тип файлов — ярлыки. Ярлык только указывает на какой-либо файл или папку, которые сами находятся в другом месте. О том, что файл является ярлыком, говорит маленькая стрелка на его значке. Ярлыки удобно помещать на Рабочий стол.Двойной щелчок на
Для чего нужны группы?
Для чего нужны группы? Группы помогают упорядочить контакты в списке. Когда контактов много, это очень удобно. По умолчанию в списке контактов ICQ уже есть две группы: Общие и Нет в списке.Чтобы создать новую группу, в главном окне программы нажмите кнопку Добавить контакт
Нетбуки: зачем они нужны и из чего выбирать Олег Нечай
Нетбуки: зачем они нужны и из чего выбирать Олег Нечай Опубликовано 29 декабря 2010 года Появившийся в октябре 2007 года недорогой субноутбук Asus Eee PC был «потребительским» ответом на «стодолларовый» ноутбук XO-1 для детей развивающихся стран,
Для чего нужны гранты Дмитрий Вибе
Для чего нужны гранты Дмитрий Вибе Опубликовано 27 августа 2013 Приближается дата, важная для российского научного мироощущения, — дата подачи заявок на гранты РФФИ. Не на все, конечно: есть много грантов, заявки на которые принимаются к другим датам