functional (C++)

18.12.2020

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

История

Впервые заголовочный файл <functional> появился в стандарте языка в 1998 году, куда был добавлен вместе со стандартной библиотекой шаблонов. Изначально в него вошел набор вспомогательных функциональных объектов для удобства использования алгоритмов STL. Также сюда вошли связыватели (binders) и набор обёрток функций, цель которых была облегчить работу в тех случаях, когда активно использовалась передача указателей на функции, то есть работа с функциями, как с некими объектами. Существенное пополнение заголовочного файла предлагалось в библиотеке расширений С++ TR1 . Из библиотеки Boost в STL переносились такие классы, как function, bind, mem_fn, result_of, reference_wrapper, hash. Большинство этих изменений, за исключением result_of, и вошло в актуальный на данный момент стандарт языка C++17. Поскольку классы function и bind во многом дублируют функциональность связывателей и обёрток функций редакции стандарта 1998-го года, то в С++11 последние были обозначены как устаревшие (deprecated).

Основные понятия

Термины стандарта

В документе стандарта языка C++11 вводятся следующие термины касаемо классов заголовочного файла <functional>.

  • Тип функционального объекта (function object type) — тип объекта, который может быть типом постфиксного выражения в вызове функции, где постфиксное выражение — набор перегруженных функций, или шаблонов функций, или адрес такого набора, или функциональный объект.
  • Сигнатура вызова (call signature) — это название возвращаемого типа за которым следует в круглых скобках список нуля или более типов аргументов.
  • Вызываемый тип (callable type) — это или тип функционального объекта, или указатель на член класса.
  • Вызываемый объект (callable object) — это объект вызываемого типа.
  • Тип обёртки вызова (call wrapper type) — это тип, который содержит вызываемый объект, и поддерживает операцию вызова, которая ведет к вызову (invoke) хранимого объекта.
  • Обёртка вызова (call wrapper) — объект типа обёртки вызова.
  • Целевой объект (target object) — вызываемый объект, который содержит обёртка вызова.

Понятие функционального объекта

Функциональный объект, или функтор — это класс с определённым оператором вызова функции — operator () таким образом, что в следующем коде

FunctionObjectType func; func();

выражение func() является вызовом operator() функционального объекта func, а не вызовом некоторой функции с именем func. Тип функционального объекта должен быть определён следующим образом:

class FunctionObjectType { public: void operator() () { // Do some work } };

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

  • Функциональный объект может иметь состояние. Фактически может быть два объекта одного и того же функционального типа, находящиеся в разных состояниях в одно и тоже время, что невозможно для обычных функций. Также функциональный объект может обеспечить операции предварительной инициализации данных.
  • Каждый функциональный объект имеет тип, а следовательно имеется возможность передать этот тип как параметр шаблона для указания определённого поведения. К примеру, типы контейнеров с разными функциональными объектами отличаются.
  • Объекты-функции зачастую выполняются быстрее чем указатели на функции. К примеру, встроить (inline) обращение к оператору () класса легче, чем функцию, переданную по указателю.
  • Предикаты

    Функциональные объекты, которые возвращают булевский тип, называются предикатами. В стандартной библиотеке используются унарные и бинарные предикаты. Поведение предиката не должно зависеть от количества выполненных операций копирования над этим предикатом, поскольку стандарт C++ не определяет, сколько раз предикат может быть скопирован при использовании в алгоритмах. Иными словами, для того, чтобы пользовательский предикат был приемлемым для STL, он не должен менять своё состояние при копировании или вызове.

    Обёртки функций

    std::function

    Начиная со стандарта C++11 шаблонный класс std::function является полиморфной обёрткой функций для общего использования. Объекты класса std::function могут хранить, копировать и вызывать произвольные вызываемые объекты — функции, лямбда-выражения, выражения связывания и другие функциональные объекты. Говоря в общем, в любом месте, где необходимо использовать указатель на функцию для её отложенного вызова, или для создания функции обратного вызова, вместо него может быть использован std::function, который предоставляет пользователю большую гибкость в реализации.

    Впервые данный класс появился в библиотеке Function в версии Boost 1.23.0. После его дальнейшей разработки он был включен в стандарт расширения C++ TR1 и окончательно утверждён в С++11.

    Определение класса

    template<class> class function; // undefined template<class R, class... ArgTypes> class function<R(ArgTypes...)>;

    Также в стандарте определены вспомогательные модификаторы swap и assign и операторы сравнения (== и !=) с nullptr. Доступ к целевому объекту предоставляет функция target, а к его типу — target_type. Оператор приведения function к булевскому типу возвращает true, когда у класса есть целевой объект.

    Пример использования

    #include <iostream> #include <functional> struct A { A(int num) : num_(num){} void printNumberLetter(char c) const {std::cout << "Number: " << num_ << " Letter: " << c << std::endl;} int num_; }; void printLetter(char c) { std::cout << c << std::endl; } struct B { void operator() () {std::cout << "B()" << std::endl;} }; int main() { // Содержит функцию. std::function<void(char)> f_print_Letter = printLetter; f_print_Letter('Q'); // Содержит лямбда-выражение. std::function<void()> f_print_Hello = [] () {std::cout << "Hello world!" << std::endl;}; f_print_Hello(); // Содержит связыватель. std::function<void()> f_print_Z = std::bind(printLetter, 'Z'); f_print_Z(); // Содержит вызов метода класса. std::function<void(const A&, char)> f_printA = &A::printNumberLetter; A a(10); f_printA(a, 'A'); // Содержит функциональный объект. B b; std::function<void()> f_B = b; f_B(); }

    Результатом работы приведённого выше кода будет:

    Q Hello world! Z Number: 10 Letter: A B()

    std::bad_functional_call

    Исключение типа bad_functional_call будет брошено при попытке вызова обёртки функции function::operator(), если у этой обёртки отсутствует целевой объект. bad_functional_call наследуется от std::exception, и у него доступен виртуальный метод what() для получения текста ошибки. Пример использования:

    #include <iostream> #include <functional> int main() { std::function<void()> func = nullptr; try { func(); } catch(const std::bad_function_call& e) { std::cout << e.what() << std::endl; } }

    std::mem_fn

    Шаблонная функция std::mem_fn создаёт объект-обёртку вокруг указателей на члены класса. Этот объект может хранить, копировать и вызывать член класса по указателю. В качестве указателя могут также использоваться ссылки и умные указатели.

    Впервые шаблонная функция std::mem_fn появилась в библиотеке Member Function в версии Boost 1.25.0. Она также была включена в C++ TR1 и окончательно в С++11. В библиотеке Boost она разрабатывалась как обобщение стандартных функций std::mem_fun и std::mem_fun_ref.

    Устаревшие базовые классы

    До включения в C++11 частей библиотеки Boost, в стандартной библиотеке были свои аналоги обёрток функций. В помощь для написании объектов-функций, библиотека предоставляет следующие базовые классы.

    template <class Arg, class Result> struct unary_function { typedef Arg argument_type; typedef Result result_type; }; template <class Arg1, class Arg2, class Result> struct binary_function { typedef Arg1 first_argument_type; typedef Arg2 second_argument_type; typedef Result result_type; };

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

    Однако, адаптивный функциональный протокол, базирующийся на наследовании, который вводили эти классы, был заменен лямбда-функциями и std::bind в С++11, и стало накладно поддерживать этот протокол для новых компонентов библиотеки. Кроме того, избавление от наследования разрешало некоторые неоднозначности. Поэтому было решено обозначить эти классы как устаревшие в С++11.

    Устаревшие адаптеры

    В стандарте имеются адаптеры указателей на функцию и адаптеры методов классов, которые в стандарте С++11 объявлены устаревшими, поскольку они дублируют функциональность нововведений.

    std::ptr_fun позволяет создавать обёртки вокруг функций от одного и двух аргументов. Одно из применений — передача глобальных функций, обёрнутых этим адаптером, алгоритмам STL. Типом возвращаемого значения являются шаблонные классы std::pointer_to_unary_function или std::pointer_to_binary_function в зависимости от количества аргументов.

    Связыватели

    std::bind

    Шаблонная функция std::bind называется связывателем и предоставляет поддержку частичного применения функций. Она привязывает некоторые аргументы к функциональному объекту, создавая новый функциональный объект. То есть вызов связывателя эквивалентен вызову функционального объекта с некоторыми определёнными параметрами. Передавать связывателю можно или непосредственно значения аргументов, или специальные имена, определенные в пространстве имен std::placeholders, которые указывают связывателю на то, что данный аргумент не будет связан, и определяют порядок аргументов у возвращаемого функционального объекта.

    Впервые данная функция появилась в библиотеке Bind в версии Boost 1.25.0. Там она позиционировалась как обобщение и расширение стандартных связывателей std::bind1st и std::bind2nd, так как позволяла связывать произвольное количество аргументов и изменять их порядок. В редакции стандарта С++11 bind был включен в библиотеку и предыдущие связыватели были обозначены устаревшими.

    Определение функции

    template<class F, class... BoundArgs> unspecified bind(F&& f, BoundArgs&&... bound_args); template<class R, class F, class... BoundArgs> unspecified bind(F&& f, BoundArgs&&... bound_args);

    Здесь f — вызываемый объект, bound_args — список связанных аргументов. Возвращаемым значением есть функциональный объект неопределённого типа T, который может быть помещён в std::function, и для которого выполняется std::is_bind_expression<T>::value == true. Внутри обёртка содержит объект типа std::decay<F>::type, построенного с std::forward<F>(f), а также по одному объекту для каждого аргумента аналогичного типа std::decay<Arg_i>::type.

    std::placeholders

    В пространстве имён std::placeholders содержатся специальные объекты _1, _2, ... , _N, где число N зависит от реализации. Они используются в функции bind для задания порядка свободных аргументов. Когда такие объекты передаются в виде аргументов в функцию bind, то для них генерируется функциональный объект, в котором, при вызове с несвязанными аргументами, каждый заполнитель _N будет заменён на N-й по счёту несвязанный аргумент.

    Для получения целого числа k из заполнителя _K предусмотрен вспомогательный шаблонный класс std::is_placeholder. При передаче ему заполнителя, как параметра шаблона, есть возможность получить целое число при обращении к его полю value. К примеру, is_placeholder<_3>::value вернёт 3.

    Пример

    #include <iostream> #include <functional> int myPlus (int a, int b) {return a + b;} int main() { std::function<int (int)> f(std::bind(myPlus, std::placeholders::_1, 5)); std::cout << f(10) << std::endl; }

    Результатом работы этого примера будет:

    15

    Устаревшие связыватели

    В редакции стандарта C++ 1998 года стандартная библиотека предоставляла связыватели std::bind1st и std::bind2nd, позволяющие функцию от двух аргументов преобразовать в функцию от одного аргумента, привязывая второй аргумент к какому-либо значению. На вход они принимают функциональный объект и значение аргумента для связывания, а возвращают шаблонные классы std::binder1st и std::binder2nd, наследники unary_function, соответственно.

    Пример использования.

    void func(list<int>& cont) { list<int>::const_iterator iter = find_if(cont.begin(), cont.end(), bind2nd(greater<int>(), 10)); // Do some work ... }

    Функциональные объекты

    Набор предопределённых функциональных объектов для базовых операций был неотъемлемой частью стандартной библиотеки шаблонов с момента её появления в стандарте. Это базовые арифметические операции (+-*/%), базовые логические операции (&&, ||, !) и операции сравнения (==, !=, >, <, >=, <=). Несмотря на их тривиальность, используя именно эти классы проводилась демонстрация возможностей алгоритмов стандартной библиотеки. Также их наличие способствует удобству и избавляет пользователя библиотеки от избыточной работы по написанию собственных аналогов. Логические функторы и функторы сравнения являются предикатами и возвращают булевский тип. Начиная с С++11, также были добавлены некоторые битовые операции (and, or, xor, not).

    Отрицатели (negators)

    Также, наряду с предопределёнными предикатами, в заголовочном файле имеются отрицатели предикатов, которые вызывают предикат и возвращают результат обратный результату предиката. Предикатные отрицатели сродни связывателям в том, что они принимают операцию и производят из неё другую операцию. Библиотека предоставляет два таких отрицателя: унарный not1() и бинарный not2(). Возвращаемым типом этих отрицателей есть специальные вспомогательные классы unary_negate и binary_negate, определенные следующим образом:

    template <class Predicate> class unary_negate { public: explicit unary_negate(const Predicate& pred); bool operator()(const typename Predicate::argument_type& x) const; }; template <class Predicate> class binary_negate { public: explicit binary_negate(const Predicate& pred); bool operator()(const typename Predicate::first_argument_type& x, const typename Predicate::second_argument_type& y) const;

    Здесь operator() возвращает !pred(x) в первом случае, и !pred(x,y) во втором. Унарный предикат должен иметь определенный тип argument_type, а бинарный — типы first_argument_type и second_argument_type. Наличие таких определений у таких классов как std::function, std::mem_fn и std::ref делает возможным использование отрицателей вместе с обёртками функций.

    В изначальной редакции стандарта unary_negate и binary_negate наследовались от базовых классов unary_function и binary_function соответственно, что предоставляло пользователю возможность использовать отрицатели для собственных предикатов. Так как упомянутые выше базовые классы были помечены устаревшими, а какой-либо замены отрицателям, помимо лямбда-функций, нет, то их решено было оставить.

    Обёртки ссылок

    В заголовочном файле <functional> определен небольшой вспомогательный класс std::reference_wrapper, который оборачивает в себе ссылку на объект, или ссылку на функцию, переданную ему в шаблоне. Он может быть полезен для передачи ссылок шаблонам функций (к примеру, в алгоритмах), которые обычно делают копии объектов при передаче по значению. Всё, что делает reference_wrapper, это хранение ссылки на переданный в шаблоне тип T, и выдачу её при обращении operator T& ().

    Впервые шаблонный класс reference_wrapper появился в библиотеке Ref в версии Boost 1.25.0. С некоторыми доработками он был включен в С++11.

    Для создания объектов reference_wrapper предоставлены вспомогательные функции ref и cref, определённые следующим образом:

    template <class T> reference_wrapper<T> ref(T& t) noexcept; template <class T> reference_wrapper<const T> cref(const T& t) noexcept;

    Имя:*
    E-Mail:
    Комментарий: