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

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

Читать бесплатно Скотт Мейерс - Эффективное использование STL. Жанр: Программирование издательство -, год 2004. Так же читаем полные версии (весь текст) онлайн без регистрации и SMS на сайте kniga-online.club или прочесть краткое содержание, предисловие (аннотацию), описание и ознакомиться с отзывами (комментариями) о произведении.
Перейти на страницу:

Предположим, вы нарушили это ограничение. Ниже приведен плохо спроектированный класс предиката, который независимо от переданных аргументов возвращает 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.

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

Скотт Мейерс читать все книги автора по порядку

Скотт Мейерс - все книги автора в одном месте читать по порядку полные версии на сайте онлайн библиотеки kniga-online.club.


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

Отзывы читателей о книге Эффективное использование STL, автор: Скотт Мейерс. Читайте комментарии и мнения людей о произведении.


Уважаемые читатели и просто посетители нашей библиотеки! Просим Вас придерживаться определенных правил при комментировании литературных произведений.

  • 1. Просьба отказаться от дискриминационных высказываний. Мы защищаем право наших читателей свободно выражать свою точку зрения. Вместе с тем мы не терпим агрессии. На сайте запрещено оставлять комментарий, который содержит унизительные высказывания или призывы к насилию по отношению к отдельным лицам или группам людей на основании их расы, этнического происхождения, вероисповедания, недееспособности, пола, возраста, статуса ветерана, касты или сексуальной ориентации.
  • 2. Просьба отказаться от оскорблений, угроз и запугиваний.
  • 3. Просьба отказаться от нецензурной лексики.
  • 4. Просьба вести себя максимально корректно как по отношению к авторам, так и по отношению к другим читателям и их комментариям.

Надеемся на Ваше понимание и благоразумие. С уважением, администратор kniga-online.


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