В предыдущих обсуждениях уже неоднократно мелькал такой термин как функтор, но особую актуальность он приобретает применительно к алгоритмам. Теперь пришло время разобраться с этим понятием. Функтор — это сокращение от функциональный объект, представляющий собой конструкцию, позволяющую использовать объект класса как функцию. В C++ для определения функтора достаточно описать класс, в котором переопределена операция ().
То, как из объекта образуется функция, легко показать на таком простом примере:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | #include <iostream> #include <vector> using namespace std; class summator : private vector<int> { public: summator(const vector<int>& ini) { for (auto x : ini) this->push_back(x); } int operator()(bool even) { int sum = 0; auto i = begin(); if (even) i++; while (i < end()) { sum += *i++; if (i == end()) break; i++; } return sum; } }; int main(void) { setlocale(LC_ALL, "rus"); summator sums(vector<int>({ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 })); cout << "сумма чётных = " << sums(true) << endl << "сумма нечётных = " << sums(false) << endl; } |
Уже из такого простого примера видно следующее: операция () в классе может быть переопределена (точнее определена, поскольку она не имеет реализации по умолчанию) с произвольным числом, типом параметров и типом возвращаемого значения (или даже вовсе без возвращаемого значения). В итоге:
Выгода функтора состоит в том, что а). его можно параметризовать при создании объекта (перед вызовом) используя конструктор объекта с параметрами и б). может создаваться временный объект исключительно на время выполнения функционального вызова. Это иллюстрируется примером такого упрощённого целочисленного калькулятора:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | #include <iostream> using namespace std; class calculate { char op; public: calculate( char op ) : op( op ) {} int operator()( int op1, int op2 ) { switch( op ) { case '+': return op1 + op2; case '-': return op1 - op2; case '*': return op1 * op2; case '/': return op1 / op2; case '%': return op1 % op2; case '^': { int ret = op1; while( op2-- > 1 ) ret *= op1; return ret; } default: cout << "неразрешённая операция" << endl; return 0; } } }; int main( int argc, char **argv, char **envp ) { setlocale(LC_ALL, "rus"); char oper; int op1, op2; do { cout << "выражение для вычисления (<op1><знак><op2>): " << flush; cin >> op1 >> oper >> op2; cout << op1 << ' ' << oper << ' ' << op2 << " = " << calculate( oper )( op1, op2 ) << endl; } while( true ); return 0; } |
Здесь в строке cout << calculate( oper )( op1, op2 ) действия выполняются последовательно:
создаётся временный объект класса calculate конструктором с параметром oper;
для этого объекта выполняется метод () (функциональный вызов) с двумя параметрами;
операция, которая будет выполнена в этом функциональном вызове, зависит от того параметра oper, с которым был сконструирован объект;
функциональный вызов возвращает значение результата операции;
созданный временный объект, сразу же после этого разрушается (если бы у него был описан деструктор, то он бы вызывался в этой точке);
И в итоге мы получаем:
Но особо широкое применение функторы приобрели в алгоритмах STL, рассмотренных ранее, когда они передаются в вызов в качестве параметра, вместо функции, определяющей действие или предикат алгоритма.
А почему в первом примере символ : стоит перед словом public? Поясните, пожалуйста)
То есть перед private)
Это уже из другого раздела C++: классы, области видимости.
В примере private относится к видимости базового класса: доступ к базовому классу существует только изнутри класса summator.
Квалификатор public дальше уже определён внутри класса, и относится к функциям членам, объявленным после него: конструктору и оператору (). Он показывает что эти функции члены видимы и могут быть использованы в любом месте программы.
В данном примере всё это не так важно, но это отдельная и важная часть языка C++.