Скотт Мейерс - Эффективное использование STL
- Название:Эффективное использование STL
- Автор:
- Жанр:
- Издательство:Питер
- Год:2002
- Город:СПб.
- ISBN:ISBN 5-94723-382-7
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
Скотт Мейерс - Эффективное использование STL краткое содержание
В этой книге известный автор Скотт Мейерс раскрывает секреты настоящих мастеров, позволяющие добиться максимальной эффективности при работе с библиотекой STL.
Во многих книгах описываются возможности STL, но только в этой рассказано о том, как работать с этой библиотекой. Каждый из 50 советов книги подкреплен анализом и убедительными примерами, поэтому читатель не только узнает, как решать ту или иную задачу, но и когда следует выбирать то или иное решение — и почему именно такое.
Эффективное использование STL - читать онлайн бесплатно полную версию (весь текст целиком)
Интервал:
Закладка:
Вернемся к определению WidgetNameCompare:
struct WidgetNameCompare:
std::binary_function{
bool operator()(const Widget& lhs,const Widget& rhs) const;
};
Хотя аргументы operator() относятся к типу const Widget&, шаблону binary_ function передается тип Widget. Обычно при передаче unary_function или binary_function типов, не являющихся указателями, ключевые слова const и знаки ссылки удаляются... только не спрашивайте, почему, — ответ на этот вопрос не интересен и не принципиален. Если вы сгораете от любопытства, напишите программу, в которой они не удаляются, и проанализируйте полученную диагностику компилятора. А если вы и после этого не утратите интерес к этой теме, посетите сайт boost.org(см. совет 50) и поищите на нем информацию об адаптерах объектов функций.
Если operator() получает параметры-указатели, ситуация меняется. Ниже приведена структура, аналогичная WidgetNameCompare, но работающая с указателями Widget*:
struct PtrWidgetNameCompare:
std::binary_function bool>{
bool operator() (const Widget*Ihs. const Widget*rhs) const;
};
В этом случае типы, передаваемые binary_function, совпадают с типами, передаваемыми operator(). Общее правило для классов функторов, получающих или возвращающих указатели, заключается в том, что unary_function или binary_ function передаются в точности те типы, которые получает или возвращает operator().
Помните, что базовые классы unary_function и binary_function выполняют только одну важную функцию — они предоставляют определения типов, необходимые для работы адаптеров, поэтому наследование от этих классов порождает адаптируемые объекты функций. Это позволяет использовать в программах следующие конструкции:
list widgets:
list::reverse_iterator il=//Найти последний объект
find_if(widgets.rbegin(),widgets.rend(), //Widget, не соответствующий
not1(MeetsThreshold(10))); //пороговому критерию 10
//(что бы это ни означало)
Widget w( аргументы конструктора): // Найти первый объект Widget.
list::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 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 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. Нетрудно представить себе возможную ее реализацию:
Читать дальшеИнтервал:
Закладка: