Программирование >>  Включение нужных заголовков 

1 ... 45 46 47 [ 48 ] 49 50 51 ... 71


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

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

tempiate<typename Fwdlterator,typename Predicate>

Fwdlterator remove if(Fwdlterator begin, Fwdlterator end. Predicate p)

begin = f i ndjf (begin, end, p):

if(begin==end) return begin: else {

Fwdlterator next=begin:

return remove copy i f(++next,end,begin,p);

Подробности нас сейчас не интересуют. Обратите внимание: предикат р сначала передается f i nd i f, a затем renrove copy i f. Конечно, в обоих слзаях р передается по значению - то есть копируется (теоретически возможны исключения, но на практике дело обстоит именно так; за подробностями обращайтесь к совету 38).

Первый вызов renrove i f (расположенный в клиентском коде, удаляющем третий элемент из vw) создает анонимный объект BadPredicate с внутренней переменной timesCalled, равной 0. Этот объект, известный в remove if под именем р, затем копируется в find if, поэтому find if тоже ползает объект BadPredicate с переменной timesCalled, равной 0. Алгоритм find if вызывает этот объект, пока тот не вернет true; таким образом, объект вызывается три раза. Затем f i nd i f возвращает управление remove i f. Remove i f продолжает выполняться и в итоге вызывает removecopyi f, передавая в качестве предиката очередную копию р. Но переменная timesCalled объекта р по-прежнему равна О! Ведь алгоритм find if вызывал не р, а лишь копию р. В результате при третьем вызове из remove copy i f предикат тоже вернет true. Теперь понятно, почему remove if удаляет два объекта Widget вместо одного.

Чтобы обойти эту лингвистическую ловушку, проще всего объявить функцию operatorO с ключевым словом const в предикатном классе. В этом слзае компилятор не позволит изменить переменные класса:

class BadPredicate:

public unary function<Widget.bool> { public:

bool operatorO (const WidgetS) const {

return ++timesCalled == 3: Ошибка! Изменение локальных данных } в константной функции невозможно

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



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

Ранее в этом совете уже упоминалось о том, что всюду, где STL ожидает получить предикатную функцию, может передаваться либо реальная функция, либо объект предикатного класса. Этот принцип действует в обоих направлениях. В любом месте, где STL рассчитывает получить объект предикатного класса, подойдет и предикатная функция (возможно, модифицированная при помощи ptr fun - см. совет 41). Теперь вы знаете, что функции operatorO в предикатных классах должны быть чистыми функциями, поэтому ограничение распространяется и на предикатные функции. Следующая функция также плоха в качестве предиката, как и объекты, созданные на основе класса BadPredicate:

bool anotherBadPredicateCconst WidgetS,const WidgetS) {

static int timesCalled = 0: Нет! Нет! Нет! Нет! Нет! Нет! return ++timesCalled == 3: Предикаты должны быть чистыми } функциями, а чистые функции

не имеют состояния

Как бы вы ни программировали предикаты, они всегда должны быть чистыми функциями.

Совет 40. Классы функторов должны быть адаптируемыми

Предположим, у нас имеется список указателей IJidget* и функция, которая по указателю определяет, является ли объект Widget интересным :

list<Widget*> WidgetPtrs:

bool isInteresting(const Widget *pw):

Если потребуется найти в списке первый указатель на интересный объект Widget, это делается легко:

1 i st<Widget*>:.- iterator i =find i f(widgetPtrs. begin(),widgetPtrs.end().

islnteresting):

if (i!=widgetPtrs.end()) {

Обработка первого интересного } указателя на Widget

С другой стороны, если потребуется найти первый указатель на неинтересный объект Widget, следующее очевидное решение не компилируется:

list<Widget*>: iterator i = fi nd i f(wi dgetPtrs.begi n 0.wi dgetPtrs.end(),

notl(is Interesting)); Ошибка! He компилируется



if (i!=widgetPtrs.end()){

Обработка первого

неинтересного указателя

на Widget

При виде этого решения невольно возникают вопросы. Почему мы должны применять ptr fun к islnteresting перед notl? Что ptrfun для нас делает и почему начинает работать приведенная выше конструкция?

Ответ оказывается весьма неожиданным. Вся работа ptr fun сводится к предоставлению нескольких определений типов. Эти определения типов необходимы для notl, поэтому применение notl к ptr fun работает, а непосредственное применение notl к islnteresting не работает. Примитивный указатель на функцию islnteresting не поддерживает определения типов, необходимые для notl.

Впрочем, notl - не единственный компонент STL, предъявляюший подобные требования. Все четыре стандартных адаптера (notl, not2, bindlst и bind2nd), а также все нестандартные STL-совместимые адаптеры из внешних источников (например, входящие в SGI и Boost - см. совет 50), требуют существования некоторых определений типов. Объекты функций, предоставляющие необходимые определения типов, называются адаптируемьши; при отсутствии этих определений объект называется неадаптируемьш. Адаптируемые объекты функций могут использоваться в контекстах, в которых невозможно использование неадаптируе-мых объектов, поэтому вы должны по возможности делать свои объекты функций адаптируемыми. Адаптируемость не требует никаких затрат, но значительно упрощает использование классов функторов клиентами.

Наверное, вместо туманного выражения некоторые определения типов вы бы предпочли иметь точный список? Речь идет об определениях argument type, fi rst argument type, second argument type и resul t type, но ситуация осложняется тем, что разные классы функторов должны предоставлять разные подмножества этих имен. Честно говоря, если вы не занимаетесь разработкой собственных адаптеров, вам вообще ничего не нужно знать об этих определениях. Как правило, определения наследуются от базового класса, а говоря точнее - от базовой структуры. Для классов функторов, у которых operatorO вызывается с одним аргументом, в качестве предка выбирается структура std: :unary function. Классы функторов, у которых operatorO вызывается с двумя аргументами, наследуют от структуры std::binary function.

Впрочем, не совсем так. unary function и binary function являются шаблонами, поэтому прямое наследование от них невозможно. Вместо этого при наследовании используются структуры, созданные на основе этих шаблонов, а для этого необходимо указать аргументы типов. Для unary function задается тип параметра, получаемого функцией operatorO вашего класса функтора, а также тип возвращаемого значения. Для binary function количество типов увеличивается до трех: типы первого и второго параметров operatorO и тип возвращаемого значения.

Перед notl к функции islnteresting необходимо применить ptr fun:

list<Widget*>::iterator i = fi nd i f(wi dgetPtrs.begi n(),wi dgetPtrs.end(),

notl(ptr fun(isInteresting))); Нормально



1 ... 45 46 47 [ 48 ] 49 50 51 ... 71

© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки.
Яндекс.Метрика