KnigaRead.com/
KnigaRead.com » Компьютеры и Интернет » Программирование » Скотт Мейерс - Эффективное использование STL

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

На нашем сайте KnigaRead.com Вы можете абсолютно бесплатно читать книгу онлайн Скотт Мейерс, "Эффективное использование STL" бесплатно, без регистрации.
Перейти на страницу:

std::binary_function<const Widget*, const Widget*.bool>{

bool operator()(const Widget* Ihs. const Widget* rhs) const;

};

В этом случае типы, передаваемые binary_function, совпадают с типами, передаваемыми operator(). Общее правило для классов функторов, получающих или возвращающих указатели, заключается в том, что unary_function или binary_ function передаются в точности те типы, которые получает или возвращает operator().

Помните, что базовые классы unary_function и binary_function выполняют только одну важную функцию — они предоставляют определения типов, необходимые для работы адаптеров, поэтому наследование от этих классов порождает адаптируемые объекты функций. Это позволяет использовать в программах следующие конструкции:

list<Widget> widgets:

list<Widget>::reverse_iterator il=//Найти последний объект

find_if(widgets.rbegin(),widgets.rend(), //Widget, не соответствующий

not1(MeetsThreshold<int>(10))); //пороговому критерию 10

//(что бы это ни означало)

Widget w(аргументы конструктора): // Найти первый объект Widget.

list<Widget>::iterator i2 =// предшествующий w в порядке

find_if(widgets.begin(),widgets.end(),// сортировки, определенном

bind2nd(WidgetNameCompare().w));// WidgetNameCompare

Если бы классы функторов не определялись производными от unary_function или binary_function, ни один из этих примеров не компилировался бы, поскольку not1 и bind2nd работают только с адаптируемыми объектами функций.

Объекты функций STL построены по образцу функций С++, а функции С++ характеризуются единственным набором типов параметров и одним типом возвращаемого значения. В результате STL неявно подразумевает, что каждый класс функтора содержит единственную функцию operator(), типы параметров и возвращаемого значения которой должны передаваться unary_function или binary_ function (с учетом правил передачи ссылок и указателей, о которых говорилось ранее). Из этого следует одно важное обстоятельство: не поддавайтесь соблазну и не пытайтесь объединять функциональность WidgetnNameCompare и PtrWidgetCompare в одной структуре с двумя функциями operator(). В этом случае функтор будет адаптируемым по отношению лишь к одной из двух форм вызова (той, что использовалась при передаче параметров binary_function), а пользы от такого решения будет немного — наполовину адаптируемый функтор ничуть не лучше неадаптируемого.

Иногда в классе функтора бывает разумно определить несколько форм вызова, тем самым отказавшись от адаптируемости (примеры таких ситуаций приведены в советах 7, 20, 23 и 25), но это скорее исключение, а не правило. Адаптируемость важна, и о ней следует помнить при разработке классов функторов.

Совет 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.

// Не компилируется.

Перейти на страницу:
Прокомментировать
Подтвердите что вы не робот:*