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

ЖАНРЫ

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

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

Шрифт:
Исключения

Хотя обычно алгоритм

partial_sort
быстрее полной сортировки (так как должен выполнять меньшее количество работы), если вам надо отсортировать почти весь (или весь) диапазон, то в этой ситуации алгоритм
sort
может оказаться быстрее.

Ссылки

[Austern99] §13.1 • [Bentley00] §11 • [Josuttis99] §9.2.2 • [Meyers01] §31 • [Musser01] §5.4, §22.26 • [Stroustrup00] §17.1.4.1, §18.7

87. Делайте предикаты чистыми функциями

Резюме

Предикат представляет собой функциональный объект, который возвращает

ответ да/нет, обычно в виде значения типа
bool
. Функция является "чистой" в математическом смысле, если ее результат зависит только от ее аргументов (обратите внимание — в данном случае термин "чистая" не имеет никакого отношения к чисто виртуальным функциям).

Не позволяйте предикатам сохранять или обращаться к состоянию так, чтобы это могло влиять на результат работы оператора

operator
; при этом понятие состояния включает как данные-члены, так и глобальные состояния. Для предикатов желательно делать оператор
operator
константной функцией-членом (см. рекомендацию 15).

Обсуждение

Алгоритмы создают неизвестное количество копий предикатов в неизвестные моменты времени и в неизвестном порядке, так что приходится полагаться на то, что все копии эквивалентны.

Именно поэтому вы отвечаете за то, чтобы все копии предикатов были эквивалентны; это означает, что все они должны быть чистыми функциями, результат работы которых полностью и однозначно определяется аргументами, передаваемыми оператору

operator
и не зависит ни от каких иных факторов. При передаче одних и тех же аргументов предикат всегда должен возвращать одно и то же значение.

Предикаты с состояниями могут показаться полезными, но они явно не очень полезны при использовании с алгоритмами стандартной библиотеки С++, и это сделано преднамеренно. В частности, предикаты с состояниями могут быть полезны только при выполнении ряда условий.

• Предикат не копируется. Стандартные алгоритмы не дают такой гарантии; в действительности алгоритмы, напротив, предполагают, что предикаты могут безопасно копироваться.

• Предикаты используются в предопределенном документированном порядке. В общем случае стандартные алгоритмы не дают никакой гарантии относительно порядка применения предикатов к элементам диапазона. При отсутствии гарантий по поводу порядка обработки элементов, операция наподобие "пометить третий элемент" (см. примеры) имеет мало смысла, поскольку не определено, какой именно элемент будет обработан третьим.

Первое условие можно обойти, написав предикат с использованием счетчика ссылок. Этот метод решает проблему копирования предикатов, поскольку в таком случае предикаты могут безопасно копироваться без изменения их семантики при применении к объектам (см. [Sutter02]). Однако обойти второе условие оказывается невозможно.

Всегда объявляйте оператор предиката

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

Примеры

Пример.

FlagNth
. Перед вами классический пример из [Sutter02], в котором выполняется попытка удалить третий элемент из контейнера
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
.

Ссылки

[Austern99] §4.2.2 • [Josuttis99] §5.8.2, §8.1.4 • [Meyers01] §39 • [Stroustrup00] §10.2.6 • [Sutter02] §2-3

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), но на сегодняшний день проблема остается нерешенной.

Поделиться с друзьями: