- Фантастика и фэнтези
- Ироническое фэнтези
- Стимпанк
- Детективная фантастика
- Киберпанк
- Попаданцы
- LitRPG
- Космоопера
- Технофэнтези
- Городская фантастика
- Русское фэнтези
- Юмористическая фантастика
- Космическая фантастика
- Боевое фэнтези
- Эпическая фантастика
- Зарубежная фантастика
- Книги магов
- Мистика
- Разная фантастика
- Разное фэнтези
- Любовное фэнтези
- Романтическая фантастика
- Городское фентези
- Историческая фантастика
- Историческое фэнтези
- Социально-философская фантастика
- Сказочная фантастика
- Иностранное фэнтези
- Романтическое фэнтези
- Ироническая фантастика
- Ужасы и Мистика
- Постапокалипсис
- Героическая фантастика
- Фэнтези
- Научная Фантастика
- Социально-психологическая
- Альтернативная история
- Боевая фантастика
- Разная литература
- Пословицы, поговорки
- Шахматы
- Визуальные искусства
- Фанфик
- Авто и ПДД
- Военное
- Отраслевые издания
- Гиды, путеводители
- Зарубежная образовательная литература
- Военная история
- Цитаты из афоризмов
- Современная литература
- Великолепные истории
- Начинающие авторы
- Современная зарубежная литература
- Боевые искусства
- Изобразительное искусство, фотография
- Кино
- Литература 19 века
- Недвижимость
- Музыка, музыканты
- Готические новеллы
- Истории из жизни
- Культура и искусство
- Спецслужбы
- Зарубежная прикладная литература
- Музыка, танцы
- Подростковая литература
- Прочее
- Военная техника, оружие
- Газеты и журналы
- Периодические издания
- Домоводство, Дом и семья
- Любовные романы
- Экономическая литература
- Научные и научно-популярные книги
- О животных
- Биохимия
- Культурология
- Юриспруденция
- Политика
- Психотерапия
- Языкознание
- Научпоп
- Медицина
- Психология, личное
- Радиотехника
- Филология
- Педагогика
- Образовательная литература
- Деловая литература
- Физика
- Науки: разное
- Химия
- Воспитание детей, педагогика
- Детская психология
- Зарубежная публицистика
- Биология
- Архитектура
- Зарубежная психология
- Науки о космосе
- Социология
- Математика
- География
- Беременность, ожидание детей
- Литературоведение
- Транспорт, военная техника
- Обществознание
- Зоология
- География
- Альтернативная медицина
- Иностранные языки
- Ветеринария
- Рефераты
- Астрология
- Биофизика
- Экология
- История Европы
- Учебники
- Шпаргалки
- Государство и право
- Ботаника
- Религиоведение
- История
- Техническая литература
- Прочая научная литература
- Психология
- Детективы и Триллеры
- Справочная литература
- Проза
- Новелла
- Юмор
- Проза
- Историческая проза
- Русская современная проза
- Советская классическая проза
- Повести
- Очерки
- Эссе
- Рассказы
- Зарубежная современная проза
- Сентиментальная проза
- Зарубежная классика
- Афоризмы
- Эпистолярная проза
- Феерия
- Семейный роман/Семейная сага
- Разное
- Антисоветская литература
- Магический реализм
- Русская классическая проза
- Современная проза
- О войне
- Контркультура
- Классическая проза
- Поэзия, Драматургия
- Детская литература
- Детская фантастика
- Детские остросюжетные
- Детские стихи
- Детские приключения
- Внеклассное чтение
- Учебная литература
- Зарубежные детские книги
- Бизнес
- Загадки
- Книги для подростков
- Школьные учебники
- Детский фольклор
- Книги для дошкольников
- Детские детективы
- Детская познавательная и развивающая литература
- Буквари
- Прочая детская литература
- Сказка
- Детская проза
- Детская образовательная литература
- Юмор
- Документальные книги
- Бизнес
- Внешнеэкономическая деятельность
- Тайм-менеджмент
- Кадровый менеджмент
- О бизнесе популярно
- Управление, подбор персонала
- Бизнес
- Менеджмент и кадры
- Продажи
- Экономика
- Ценные бумаги и инвестиции
- Малый бизнес
- Делопроизводство, офис
- Корпоративная культура, бизнес
- Банковское дело
- Финансы
- Зарубежная деловая литература
- Ораторское искусство / риторика
- Личные финансы
- Интернет-бизнес
- Поиск работы
- Государственное и муниципальное управление
- Бухучет и аудит
- Работа с клиентами
- Менеджмент
- Краткое содержание
- Личная эффективность
- Переговоры
- Приключения
- Старинная литература
- Религия и духовность
- Компьютеры и Интернет
- Блог
Читаем без скачивания Эффективное использование STL - Скотт Мейерс
Шрифт:
Интервал:
Закладка:
Предположим, вы нарушили это ограничение. Ниже приведен плохо спроектированный класс предиката, который независимо от переданных аргументов возвращает true только один раз — при третьем вызове. Во всех остальных случаях возвращается false.
class BadPredicate: // Базовый класс описан
public unary_function<Widget, bool>{ // в совете 40
public:
BadPredicate(): timesCalles(0) {} // Переменная timesCalled
// инициализируется нулем
bool operator()(const Widget&) {
return ++timesCalled = 3;
}
private:
size_t timesCalled;
};
Предположим, класс BadPedicate используется для исключения третьего объекта Widget из контейнера vector<Widget>:
vector<Widget> vw; // Создать вектор и заполнить его
… // объектами Widget
vww.erase(remove_if(vw.begin(), // Удалить третий объект Widget.
vw.end(), // связь между erase и remove_if
BadPredcate()), // описана в совете 32
vw.end());
Программа выглядит вполне разумно, однако во многих реализациях STL из вектора vw удаляется не только третий, но и шестой элемент!
Чтобы понять, почему это происходит, необходимо рассмотреть один из распространенных вариантов реализации remove_if. Помните, что эта реализация не является обязательной.
template<typename FwdIterator, typename Predicate>
FwdIterator remove_if(FwdIterator begin, FwdIterator end, Predicate p) {
begin = find_if(begin, end, p);
if (begin==end) return begin;
else {
FwdIterator next=begin;
return remove_copy_if(++next, end, begin, p);
}
}
Подробности нас сейчас не интересуют. Обратите внимание: предикат p сначала передается find_if, а затем remove_copy_if. Конечно, в обоих случаях p передается по значению — то есть копируется (теоретически возможны исключения, но на практике дело обстоит именно так; за подробностями обращайтесь к совету 38).
Первый вызов remove_if (расположенный в клиентском коде, удаляющем третий элемент из vw) создает анонимный объект BadPredcate с внутренней переменной timesCalled, равной 0. Этот объект, известный в remove_if под именем p, затем копируется в find_if, поэтому find_if тоже получает объект BadPredicate с переменной timesCalled, равной 0. Алгоритм find_if «вызывает» этот объект, пока тот не вернет true; таким образом, объект вызывается три раза. Затем find_if возвращает управление remove_if. Remove_if продолжает выполняться и в итоге вызывает remove_copy_if, передавая в качестве предиката очередную копию p. Но переменная timesCalled объекта p по-прежнему равна 0! Ведь алгоритм find_if вызывал не p, а лишь копию p. В результате при третьем вызове из remove_copy_if предикат тоже вернет true. Теперь понятно, почему remove_if удаляет два объекта Widget вместо одного.
Чтобы обойти эту лингвистическую ловушку, проще всего объявить функцию operator() с ключевым словом const в предикатном классе. В этом случае компилятор не позволит изменить переменные класса:
class BadPredicate:
public unary_function<Widget, bool> {
public:
bool operator()(const Widget&) const {
return ++timesCalled == 3; // Ошибка! Изменение локальных данных
} // в константной функции невозможно
};
Из-за простоты этого решения я чуть было не озаглавил этот совет «Объявляйте operator() константным в предикатных классах», но этой формулировки недостаточно. Даже константные функции могут обращаться к mutablе-переменным, неконстантным локальным статическим объектам, неконстантным статическим объектам класса, неконстантным объектам в области видимости пространства имен и неконстантным глобальным объектам. Хорошо спроектированный предикатный класс должен обеспечить независимость функций operator() и от этих объектов. Объявление константных функций operator() в предикатных классах необходимо для правильного поведения, но не достаточно. Правильно написанная функция operator() является константной, но это еще не все. Она должна быть «чистой» функцией.
Ранее в этом совете уже упоминалось о том, что всюду, где STL ожидает получить предикатную функцию, может передаваться либо реальная функция, либо объект предикатного класса. Этот принцип действует в обоих направлениях. В любом месте, где STL рассчитывает получить объект предикатного класса, подойдет и предикатная функция (возможно, модифицированная при помощи ptr_fun — см. совет 41). Теперь вы знаете, что функции operator() в предикатных классах должны быть «чистыми» функциями, поэтому ограничение распространяется и на предикатные функции. Следующая функция также плоха в качестве предиката, как и объекты, созданные на основе класса BadPredcate:
bool anotherBadPredicate(const Widget&, const Widget&) {
static int timesCalled = 0; // Нет! Нет! Нет! Нет! Нет! Нет!
return ++timesCalled == 3; // Предикаты должны быть "чистыми"
} // функциями, а "чистые" функции
// не имеют состояния
Как бы вы ни программировали предикаты, они всегда должны быть «чистыми» функциями.
Совет 40. Классы функторов должны быть адаптируемыми
Предположим, у нас имеется список указателей Widget* и функция, которая по указателю определяет, является ли объект Widget «интересным»:
list<Widget*> WidgetPtrs;
bool isInteresting(const Widget *pw);
Если потребуется найти в списке первый указатель на «интересный» объект Widget, это делается легко:
list<Widget*>::iterator i = find_if(widgetPts.begin(), widgetPts.end(),
isIntersting);
if (i != widgetPts.end()) {
… // Обработка первого "интересного"
} // указателя на Widget
С другой стороны, если потребуется найти первый указатель на «неинтересный» объект Widget, следующее очевидное решение не компилируется:
list<Widget*>::iterator i = find_if(widgetPtrs.begin(), widgetPtrs.end(),
not1(isInteresting)); // Ошибка! He компилируется
Перед not1 к функции isInteresting необходимо применить ptr_fun:
list<Widget*>::iterator i =
find_if(widgetPtrs.begin(), widgetPtrs.end(),
not1(ptr_fun(isInteresting))); // Нормально
if (i != widgetPtrs.end()) { // Обработка первого
… // "неинтересного" указателя
} //на Widget
При виде этого решения невольно возникают вопросы. Почему мы должны применять ptr_fun к isInteresting перед not1? Что ptr_fun для нас делает и почему начинает работать приведенная выше конструкция?
Ответ оказывается весьма неожиданным. Вся работа ptr_fun сводится к предоставлению нескольких определений типов. Эти определения типов необходимы для not1, поэтому применение not1 к ptr_fun работает, а непосредственное применение not1 к isInteresting не работает. Примитивный указатель на функцию isInteresting не поддерживает определения типов, необходимые для not1.
Впрочем, not1 — не единственный компонент STL, предъявляющий подобные требования. Все четыре стандартных адаптера (not1, not2, bind1st и bind2nd), а также все нестандартные STL-совместимые адаптеры из внешних источников (например, входящие в SGI и Boost — см. совет 50), требуют существования некоторых определений типов. Объекты функций, предоставляющие необходимые определения типов, называются адаптируемыми; при отсутствии этих определений объект называется неадаптируемым. Адаптируемые объекты функций могут использоваться в контекстах, в которых невозможно использование неадаптируемых объектов, поэтому вы должны по возможности делать свои объекты функций адаптируемыми. Адаптируемость не требует никаких затрат, но значительно упрощает использование классов функторов клиентами.
Наверное, вместо туманного выражения «некоторые определения типов» вы бы предпочли иметь точный список? Речь идет об определениях argument_type, first_argument_type, second_argument_type и result_type, но ситуация осложняется тем, что разные классы функторов должны предоставлять разные подмножества этих имен. Честно говоря, если вы не занимаетесь разработкой собственных адаптеров, вам вообще ничего не нужно знать об этих определениях. Как правило, определения наследуются от базового класса, а говоря точнее — от базовой структуры. Для классов функторов, у которых operator() вызывается с одним аргументом, в качестве предка выбирается структура std::unary_function. Классы функторов, у которых operator() вызывается с двумя аргументами, наследуют от структуры std::binary_function.