в действительности алгоритмы, напротив, предполагают, что предикаты могут безопасно копироваться.
•
Первое условие можно обойти, написав предикат с использованием счетчика ссылок. Этот метод решает проблему копирования предикатов, поскольку в таком случае предикаты могут безопасно копироваться без изменения их семантики при применении к объектам (см. [Sutter02]). Однако обойти второе условие оказывается невозможно.
Всегда объявляйте оператор предиката operator()
как константную функцию-член, чтобы компилятор мог помочь вам избежать неприятностей, выводя сообщение об ошибке при попытках изменить любые данные-члены, которые могут быть у предиката. Это не позволяет пресечь все злоупотребления, например, доступ к глобальным данным, но, по крайней мере, поможет избежать наиболее распространенных ошибок.
v
.
class FlagNth {
public:
FlagNth(size_t n) : current_(0), n_(n) { }
// Возвращаем значение true только при третьем вызове
template<typename T>
bool operator()(const T&) // Плохо: неконстантная
{ return ++current_ == n_; } // функция
private:
size_t current_, n_;
};
// ... позже ...
v.erase(remove_if(v.begin(), v.end(), FlagNth(3)));
Увы, нет никакой гарантии, что будет удален именно третий элемент В большинстве реальных реализаций STL приведенный код наряду с третьим удалит и шестой элемент. Почему? Потому что remove_if
обычно реализуется с использованием find_if
и remove_copy_if
, и копия предиката передается каждой из этих функций.
Концептуально этот пример неверен, поскольку алгоритм remove_if
гарантирует только то, что он удалит все элементы, удовлетворяющие некоторому критерию. Он не документирует порядок, в котором совершается обход или удаление элементов из обрабатываемого диапазона, так что приведенный код использует предположение, которое не документировано и, более того, не выполняется.
Корректный способ удаления третьего элемента — выполнить итерации для его поиска и вызвать функцию erase
.
88. В качестве аргументов алгоритмов и компараторов лучше использовать функциональные объекты, а не функции
Предпочтительно передавать алгоритмам функциональные объекты, а не функции, а компараторы ассоциативных контейнеров просто должны быть функциональными объектами. Функциональные объекты адаптируемы и, вопреки ожиданиям, обычно дают более быстрый по сравнению с функциями код.
Во-первых, функциональные объекты легко сделать адаптируемыми (и такими их и следует делать — см. рекомендацию 89). Даже если у вас есть готовая функция, иногда для ее использования требуется 'обертка' из ptr_fun
или mem_fun
. Например, такая обертка требуется при построении более сложных выражений с использованием связывателей (см. также рекомендацию 84):
inline bool isHeavy(const Thing&) { /* ... */ }
find_if(v.begin(), v.end(), not1(IsHeavy)); // Ошибка
Обойти эту ошибку обычно можно путем применения ptr_fun
(или, в случае функции-члена, mem_fun
или mem_fun_ref
), что, к сожалению, не работает в данном конкретном случае:
inline bool IsHeavy(const Thing&) { /* ... */ }
find_if(v.begin(), v.end(),
not1(ptr_fun(IsHeavy))); // Героическая попытка...
Беда в том, что этот способ не будет работать, даже если вы явно укажете аргументы шаблона ptr_fun
. Коротко говоря, проблема в том, что ptr_fun
точно выводит типы аргументов и возвращаемый тип (в частности, тип параметра будет выведен как const Thing&
) и создает внутренний механизм, который, в свою очередь, пытается добавить другой &
, а ссылка на ссылку в настоящее время в C++ не разрешена. Имеются способы исправлений языка и/или библиотека для решения данной проблемы (например, позволяя ссылке на ссылку свернуться в обычную ссылку; см. также рекомендацию 89), но на сегодняшний день проблема остается нерешенной.
Прибегать ко всем этим ухищрениям совершенно излишне, если у вас имеется корректно написанный функциональный объект (см. рекомендацию 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;
};