Чтение онлайн

ЖАНРЫ

Стандарты программирования на С++. 101 правило и рекомендация

Александреску Андрей

Шрифт:

Прибегать ко всем этим ухищрениям совершенно излишне, если у вас имеется корректно написанный функциональный объект (см. рекомендацию 89), который можно использовать без какого-либо специального синтаксиса:

struct IsHeavy : unary_function<Thing, bool> {

 bool operator(const Thing&) const { /* ... */ }

};

find_if(v.begin, v.end, not1(IsHeavy)) ; // OK

Еще более важно то, что для определения сравнения в ассоциативных контейнерах вам нужен именно функциональный объект, а не функция. Это связано с тем, что нельзя инстанцировать

шаблон с функцией в качестве параметра:

bool CompareThings(const Thing&, const Thing&);

set<Thing, CompareThings> s; // Ошибка

Вместо этого следует написать:

struct CompareThings

 : public binary_function<Thing,Thing,bool> {

 bool operator( const Thing&, const Thing& ) const;

};

set<Thing, CompareThings> s; //OK

Наконец, имеется еще одно преимущество функциональных объектов — эффективность. Рассмотрим следующий знакомый алгоритм:

template<typename Iter, typename Compare>

Iter find_if(Iter first, Iter last, Compare comp);

Если мы передадим алгоритму в качестве компаратора функцию

inline bool Function(const Thing&) { /* ... */ }

find_if(v.begin, v.end, Function);

то на самом деле будет передана ссылка на функцию. Компиляторы редко встраивают вызовы таких функций (за исключением некоторых относительно свежих компиляторов, которые в состоянии провести анализ всей программы в целом), даже если они объявлены как таковые и видимы в момент компиляции вызова

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

Давайте передадим алгоритму

find_if
в качестве компаратора функциональный объект:

struct FunctionObject : unary_function<Thing, bool> {

 bool operator(const Thing&) const { /* ... */ }

};

find_if(v.begin, v.end, FunctionObject);

Если мы передаем объект, который имеет (явно или неявно) встраиваемый оператор

operator
, то такие вызовы компиляторы С++ способны делать встраиваемыми уже очень давно.

Примечание. Эта методика не является преждевременной оптимизацией (см. рекомендацию 8); ее следует рассматривать как препятствие преждевременной пессимизации (см. рекомендацию 9). Если у вас имеется готовая функция — передавайте указатель на нее (кроме тех ситуаций, когда вы должны обязательно обернуть ее в

ptr_fun
или
mem_fun
). Но если вы пишете новый код для использования в качестве аргумента алгоритма, то лучше сделать его функциональным объектом.

Ссылки

[Austern99] §4, §8, §15 • [Josuttis99] §5.9 • [Meyers01] §46 • [Musser01] §8 • [Sutter04] §25

89. Корректно пишите функциональные объекты

Резюме

Разрабатывайте функциональные объекты так, чтобы их копирование выполнялось как можно эффективнее. Там, где это возможно, делайте их максимально адаптируемыми путем наследования от

unary_function
или
binary_function
.

Обсуждение

Функциональные объекты моделируют указатели на функции. Подобно указателям

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

template<class InputIter, class Func>

Function for_each(InputIter first, InputIter last, Function f);

Следовательно, функциональные объекты должны легко копироваться и быть мономорфными (для защиты от срезки), так что избегайте виртуальных функций (см. рекомендацию 54). Конечно, у вас могут быть большие и/или полиморфные функциональные объекты — их тоже вполне можно использовать; просто скройте их размер с помощью идиомы Pimpl (указателя на реализацию; см. рекомендацию 43). Эта идиома позволяет, как и требуется, получить внешний мономорфный класс малого размера, обеспечивающий доступ к богатой функциональности. Внешний класс должен удовлетворять следующим условиям.

• Быть адаптируемым. Наследуйте его от

unary_function
или
binary_function
.

• Использовать идиому Pimpl. Такой класс содержит указатель (например,

shared_ptr
) на (возможно, большого размера) реализацию необходимой функциональности.

• Иметь оператор(ы) вызова функции. Эти операторы передают вызовы объекту-реализации.

Этим ограничиваются требования к внешнему классу (не считая возможного наличия собственных (не генерируемых компилятором) конструкторов, оператора присваивания и/или деструктора.

Функциональные объекты должны быть адаптируемы. Стандартные связыватели и адаптеры полагаются на наличие определенных инструкций

typedef
, обеспечить которые легче всего при наследовании ваших функциональных объектов от
unary_function
или
binary_function
. Инстанцируйте
unary_function
или
binary_function
с теми типами, которые получает и возвращает ваш оператор
operator
(при этом у каждого типа, не являющегося указателем, следует убрать все спецификаторы
const
верхнего уровня, а также все
&
).

Постарайтесь избежать наличия нескольких операторов

operator
, поскольку это затрудняет адаптируемость. Дело в том, что обычно оказывается невозможно обеспечить корректные инструкции
typedef
, необходимые для адаптирования, поскольку один и тот же синоним типа, определяемый через инструкцию
typedef
, имеет разные значения для разных операторов
operator
.

Не все функциональные объекты являются предикатами — предикаты представляют собой подмножество функциональных объектов (см. рекомендацию 87).

Ссылки

[Allison98] §15, §C • [Austern99] §4, §8, §15 • [Gamma95] Bridge • [Josuttis99] §8.2.4 • [Koenig97] §21, §29 • [Meyers97] §34 • [Meyers01] §38, §40, §46 • [Musser01] §2.4, §8, §23 • [Sutter00] §26-30 • [Vandevoorde03] §22

Безопасность типов

Если вы лжете компилятору, он будет мстить.

— Генри Спенсер (Henry Spencer)

Всегда будут вещи, которые мы будем хотеть сказать в наших программах и которые трудно сформулировать на любом языке программирования.

— Алан Перлис (Alan Perlis)
Поделиться с друзьями: