class F { // абстрактный пример объекта-функции
S s; // состояние
public:
F(const S& ss):s(ss) { /* устанавливает начальное значение */ }
T operator() (const S& ss) const
{
// делает что-то с аргументом ss
// возвращает значение типа T (часто T — это void,
// bool или S)
}
const S& state() const { return s; } // демонстрирует
// состояние
void reset(const S& ss) { s = ss; } // восстанавливает
// состояние
};
Объект класса F
хранит данные в своем члене s
. По мере необходимости объект-функция может иметь много данных-членов. Иногда вместо фразы “что-то хранит данные” говорят “нечто пребывает в состоянии”. Когда мы создаем объект класса F
, мы можем инициализировать это состояние. При необходимости мы можем прочитать это состояние. В классе F
для считывания состояния предусмотрена операция state()
, а для записи состояния — операция reset()
. Однако при разработке объекта-функции мы свободны в выборе способа доступа к его состоянию.
Разумеется, мы можем прямо или косвенно вызывать объект-функцию, используя обычную систему обозначений. При вызове объект-функция F
получает один аргумент, но мы можем определять объекты-функции, получающие столько параметров, сколько потребуется.
<
) и определен как подставляемая функция (например, если его определение содержится в теле класса). Большинство примеров в этой главе — и в книге в целом — соответствует этому правилу. Основная причина высокой производительности небольших и простых объектов-функций состоит в том, что они предоставляют компилятору объем информации о типе, достаточный для того, чтобы сгенерировать оптимальный код. Даже устаревшие компиляторы с несложными оптимизаторами могут генерировать простую машинную инструкцию “больше” для сравнения в классе Larger_than
, вместо вызова функции. Вызов функции обычно выполняется в 10–50 раз дольше, чем простая операция сравнения. Кроме того, код для вызова функции больше, чем код простого сравнения.
21.4.2. Предикаты на членах класса
Как мы уже видели, стандартные алгоритмы хорошо работают с последовательностями элементов базовых типов, таких как int
и double
. Однако в некоторых предметных областях более широко используются контейнеры объектов пользовательских классов. Рассмотрим пример, играющий главную роль во многих областях, — сортировка записей по нескольким критериям.
struct Record {
string name; // стандартная строка
char addr[24]; // старый стиль для согласованности
// с базами данных
// ...
};
vector
Иногда мы хотим сортировать вектор vr
по имени, а иногда — по адресам. Если мы не стремимся одновременно к элегантности и эффективности, наши методы ограничены практической целесообразностью. Мы можем написать следующий код:
// ...
sort(vr.begin(),vr.end(),Cmp_by_name()); // сортировка по имени
// ...
sort(vr.begin(),vr.end(),Cmp_by_addr()); // сортировка по адресу
// ...