Язык программирования C++. Пятое издание - Стенли Липпман
Шрифт:
Интервал:
Закладка:
Вызов функции stable_sort() можно переписать так, чтобы использовать этот класс вместо лямбда-выражения:
stable_sort(words.begin(), words.end(), ShorterString());
Третий аргумент — недавно созданный составной объект класса ShorterString. Код в функции stable_sort() будет вызывать этот объект каждый раз, когда он сравнивает две строки. При вызове объекта будет выполнено тело его оператора вызова, возвращающего значение true, если размер первой строки будет меньше, чем второй.
Классы, представляющие лямбда-выражения с захваченными переменнымиКак уже упоминалось, при захвате лямбда-выражением переменной по ссылке разработчик должен сам гарантировать существование переменной, на которую ссылается ссылка, во время выполнения лямбда-выражения (см. раздел 10.3.3). Поэтому компилятору разрешено использовать ссылку непосредственно, не сохраняя ее как переменную-член в созданном классе.
Переменные, которые захватываются по значению, напротив, копируются в лямбда-выражение (см. раздел 10.3.3). В результате классы, созданные из лямбда-выражений, переменные которых захватываются по значению, имеют переменные-члены, соответствующие каждой такой переменной. У этих классов есть также конструктор для инициализации этих переменных-членов значениями захваченных переменных. В примере раздела 10.3.2 лямбда-выражение использовалось для поиска первой строки, длина которой была больше или равна заданному значению:
// получить итератор на первый элемент, размер которого >= sz
auto wc = find_if(words.begin(), words.end(),
[sz](const string &a)
Созданный класс выглядел бы примерно так:
class SizeComp {
SizeComp(size_t n) : sz(n) {} // параметр для каждой захваченной
// переменной
// оператор вызова с тем же типом возвращаемого значения, параметрами
// и телом, как у лямбда-выражения
bool operator()(const string &s) const
{ return s.size() >= sz; }
private:
size_t sz; // переменная-член для каждой переменной, захваченной
// по значению
};
В отличие от класса ShorterString, у этого класса есть переменная-член и конструктор для ее инициализации. У этого синтезируемого класса нет стандартного конструктора; чтобы использовать этот класс, следует передать аргумент:
// получить итератор на первый элемент, размер которого >= sz
auto wc = find_if(words.begin(), words.end(), SizeComp(sz));
У классов, созданных из лямбда-выражения, есть удаленный стандартный конструктор, удаленные операторы присвоения и стандартный деструктор. Будет ли у класса стандартный или удаленный конструктор копий/перемещения, зависит обычно от способа и типа захватываемых переменных-членов (см. раздел 13.1.6 и раздел 13.6.2).
Упражнения раздела 14.8.1Упражнение 14.38. Напишите класс, проверяющий соответствие длины заданной строки указанному значению. Используйте такой объект в программе для оповещения о количестве слов во входном файле, имеющих размеры от 1 до 10 включительно.
Упражнение 14.39. Перепишите предыдущую программу так, чтобы сообщать количество слов размером от 1 до 9 и 10 или более.
Упражнение 14.40. Перепишите функцию biggies() из раздела 10.3.2 так, чтобы использовать объект функции вместо лямбда-выражения.
Упражнение 14.41. Как по-вашему, существенно ли добавление лямбда-выражений по новому стандарту? Объясните, когда имеет смысл использовать лямбда-выражение, а когда класс вместо него.
14.8.2. Библиотечные объекты функций
Стандартная библиотека определяет набор классов, представляющих арифметические, реляционные и логические операторы. Каждый класс определяет оператор вызова, который применяет одноименный оператор. Например, у класса plus есть оператор вызова функции, который применяет оператор + к паре операндов; класс modulus определяет оператор вызова, применяющий бинарный оператор %; класс equal_to применяет оператор ==; и т.д.
Эти классы являются шаблонами, которым передается один тип. Он определяет тип параметра оператора вызова. Например, класс plus<string> применяет строковый оператор суммы к объектам класса string; у класса plus<int> типом операндов будет int; класс plus<Sales_data> применяет оператор + к объектам класса Sales_data; и т.д.:
plus<int> intAdd; // объект функции, способный сложить
// два значения типа int
negate<int> intNegate; // объект функции, способный изменить знак
// значения типа int
// использование оператора intAdd::operator(int, int) для
// сложения чисел 10 и 20
int sum = intAdd(10, 20); // эквивалент sum = 30
sum = intNegate(intAdd(10, 20)); // эквивалент sum = 30
// использование оператора intNegate::operator(int) для создания
// числа -10 как второго параметра выражения intAdd::operator(int, int)
sum = intAdd(10, intNegate(10)); // sum = 0
Эти типы, перечислены в табл. 4.2, определены в заголовке functional.
Таблица 14.2. Библиотечные объекты функций
Арифметические Реляционные Логические plus<Type> equal_to<Type> logical_and<Type> minus<Type> not_equal_to<Type> logical_or<Type> multiplies<Type> greater<Type> logical_not<Type> divides<Type> greater_equal<Type> modulus<Type> less<Type> negate<Type> less_equal<Type> Применение библиотечного объекта функции с алгоритмамиКлассы объектов функций, представляющие операторы, зачастую используются для переопределения заданного по умолчанию оператора, используемого алгоритмом. Как уже упоминалось, по умолчанию алгоритмы сортировки используют оператор operator< для сортировки последовательности в порядке возрастания. Для сортировки в порядке убывания можно передать объект типа greater. Этот класс создает оператор вызова, который вызывает оператор "больше" основного типа элемента. Предположим, например, что svec — это вектор типа vector<string>:
// передает временный объект функции, который применяет
// оператор > к двум строкам
sort(svec.begin(), svec.end(), greater<string>());
Это сортирует вектор в порядке убывания. Третий аргумент — безымянный объект типа greater<string>. Когда функция sort() сравнит элементы, вместо оператора < типа элемента она применит переданный объект функции greater. Этот объект применит оператор > к элементам типа string.
Одним из важнейших аспектов этих библиотечных объектов функций является то, что библиотека гарантирует их работоспособность с указателями. Помните, что результат сравнения двух несвязанных указателей непредсказуем (см. раздел 3.5.3). Но может понадобиться сортировать вектор указателей на основании их адреса в памяти. Хотя сделать это самостоятельно непросто, вполне можно применить один из библиотечных объектов функции:
vector<string *> nameTable; // вектор указателей
// ошибка: указатели в nameTable не связаны, результат < непредсказуем
sort(nameTable.begin(), nameTable.end(),
[](string *a, string *b) { return a < b; });
// ok: библиотека гарантирует, что less для типов указателя определен
sort(nameTable.begin(), nameTable.end(), less<string*>());
Стоит также обратить внимание на то, что ассоциативные контейнеры используют для упорядочивания своих элементов объект типа less<key_type>. В результате можно определить набор (set) указателей или использовать указатель как ключ в карте (map) без необходимости определять тип less самостоятельно.