Читаем Эффективное использование STL полностью

Предположим, вы нарушили это ограничение. Ниже приведен плохо спроектированный класс предиката, который независимо от переданных аргументов возвращает true только один раз — при третьем вызове. Во всех остальных случаях возвращается false.

class BadPredicate:                   // Базовый класс описан

 public unary_function{ // в совете 40

public:

 BadPredicate: timesCalles(0) {} // Переменная timesCalled

                                   // инициализируется нулем

bool operator(const Widget&) {

 return ++timesCalled = 3;

}

private:

 size_t timesCalled;

};

Предположим, класс BadPedicate используется для исключения третьего объекта Widget из контейнера vector:

vector vw; // Создать вектор и заполнить его

…                  // объектами Widget

vww.erase(remove_if(vw.begin, // Удалить третий объект Widget.

 vw.end,                      // связь между erase и remove_if

 BadPredcate),                // описана в совете 32

 vw.end);

Программа выглядит вполне разумно, однако во многих реализациях STL из вектора vw удаляется не только третий, но и шестой элемент!

Чтобы понять, почему это происходит, необходимо рассмотреть один из распространенных вариантов реализации remove_if. Помните, что эта реализация не является обязательной.

template

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 {

public:

 bool operator(const Widget&) const {

  return ++timesCalled == 3; // Ошибка! Изменение локальных данных

 }                           // в константной функции невозможно

};

Из-за простоты этого решения я чуть было не озаглавил этот совет «Объявляйте operator константным в предикатных классах», но этой формулировки недостаточно. Даже константные функции могут обращаться к mutablе-переменным, неконстантным локальным статическим объектам, неконстантным статическим объектам класса, неконстантным объектам в области видимости пространства имен и неконстантным глобальным объектам. Хорошо спроектированный предикатный класс должен обеспечить независимость функций operator и от этих объектов. Объявление константных функций operator в предикатных классах необходимо для правильного поведения, но не достаточно. Правильно написанная функция operator является константной, но это еще не все. Она должна быть «чистой» функцией.

Перейти на страницу:

Похожие книги

Основы программирования в Linux
Основы программирования в Linux

В четвертом издании популярного руководства даны основы программирования в операционной системе Linux. Рассмотрены: использование библиотек C/C++ и стан­дартных средств разработки, организация системных вызовов, файловый ввод/вывод, взаимодействие процессов, программирование средствами командной оболочки, создание графических пользовательских интерфейсов с помощью инструментальных средств GTK+ или Qt, применение сокетов и др. Описана компиляция программ, их компоновка c библиотеками и работа с терминальным вводом/выводом. Даны приемы написания приложений в средах GNOME® и KDE®, хранения данных с использованием СУБД MySQL® и отладки программ. Книга хорошо структурирована, что делает обучение легким и быстрым. Для начинающих Linux-программистов

Нейл Мэтью , Ричард Стоунс , Татьяна Коротяева

ОС и Сети / Программирование / Книги по IT
97 этюдов для архитекторов программных систем
97 этюдов для архитекторов программных систем

Успешная карьера архитектора программного обеспечения требует хорошего владения как технической, так и деловой сторонами вопросов, связанных с проектированием архитектуры. В этой необычной книге ведущие архитекторы ПО со всего света обсуждают важные принципы разработки, выходящие далеко за пределы чисто технических вопросов.?Архитектор ПО выполняет роль посредника между командой разработчиков и бизнес-руководством компании, поэтому чтобы добиться успеха в этой профессии, необходимо не только овладеть различными технологиями, но и обеспечить работу над проектом в соответствии с бизнес-целями. В книге более 50 архитекторов рассказывают о том, что считают самым важным в своей работе, дают советы, как организовать общение с другими участниками проекта, как снизить сложность архитектуры, как оказывать поддержку разработчикам. Они щедро делятся множеством полезных идей и приемов, которые вынесли из своего многолетнего опыта. Авторы надеются, что книга станет источником вдохновения и руководством к действию для многих профессиональных программистов.

Билл де Ора , Майкл Хайгард , Нил Форд

Программирование, программы, базы данных / Базы данных / Программирование / Книги по IT
Программист-прагматик. Путь от подмастерья к мастеру
Программист-прагматик. Путь от подмастерья к мастеру

Находясь на переднем крае программирования, книга "Программист-прагматик. Путь от подмастерья к мастеру" абстрагируется от всевозрастающей специализации и технических тонкостей разработки программ на современном уровне, чтобы исследовать суть процесса – требования к работоспособной и поддерживаемой программе, приводящей пользователей в восторг. Книга охватывает различные темы – от личной ответственности и карьерного роста до архитектурных методик, придающих программам гибкость и простоту в адаптации и повторном использовании.Прочитав эту книгу, вы научитесь:Бороться с недостатками программного обеспечения;Избегать ловушек, связанных с дублированием знания;Создавать гибкие, динамичные и адаптируемые программы;Избегать программирования в расчете на совпадение;Защищать вашу программу при помощи контрактов, утверждений и исключений;Собирать реальные требования;Осуществлять безжалостное и эффективное тестирование;Приводить в восторг ваших пользователей;Формировать команды из программистов-прагматиков и с помощью автоматизации делать ваши разработки более точными.

А. Алексашин , Дэвид Томас , Эндрю Хант

Программирование / Книги по IT