functional (C++)

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

История

править

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

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

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

Предикаты

править

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

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

править

std::function

править

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

Впервые данный класс появился в библиотеке Function в версии Boost 1.23.0[7]. После его дальнейшей разработки он был включен в стандарт расширения C++ TR1 и окончательно утверждён в C++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 создаёт объект-обёртку вокруг указателей на члены класса. Этот объект может хранить, копировать и вызывать член класса по указателю. В качестве указателя могут также использоваться ссылки и умные указатели[8].

Впервые шаблонная функция std::mem_fn появилась в библиотеке Member Function в версии Boost 1.25.0[7]. Она также была включена в C++ TR1 и окончательно в C++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, в частности, пользовательские предикаты полезны, когда необходимо воспользоваться алгоритмами для классов, разработанных не на основе стандартной библиотеки[6].

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

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

править

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

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

Связыватели

править

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

Впервые данная функция появилась в библиотеке Bind в версии Boost 1.25.0[7]. Там она позиционировалась как обобщение и расширение стандартных связывателей std::bind1st и std::bind2nd, так как позволяла связывать произвольное количество аргументов и изменять их порядок. В редакции стандарта C++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 ...
}

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

править

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

Тип Название Кол-во операндов Возвращаемый тип Действие
Сравнения equal_to Бинарный bool x == y
not_equal_to Бинарный bool x != y
greater Бинарный bool x > y
less Бинарный bool x < y
greater_equal Бинарный bool x >= y
less_equal Бинарный bool x <= y
Логические logical_and Бинарный bool x && y
logical_or Бинарный bool x || y
logical_not Унарный bool !x
Арифметические plus Бинарный T x + y
minus Бинарный T x - y
multiplies Бинарный T x * y
divides Бинарный T x / y
modulus Бинарный T x % y
negate Унарный T -x
Битовые (C++11) bit_and Бинарный T x & y
bit_or Бинарный T x | y
bit_xor Бинарный T x ^ y
bit_not Унарный T ~x

Отрицатели (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 соответственно, что предоставляло пользователю возможность использовать отрицатели для собственных предикатов. Так как упомянутые выше базовые классы были помечены устаревшими, а какой-либо замены отрицателям, помимо лямбда-функций, нет[11], то их решено было оставить.

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

править

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

Впервые шаблонный класс reference_wrapper появился в библиотеке Ref в версии Boost 1.25.0[7]. С некоторыми доработками он был включен в C++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;

См. также

править

Примечания

править
  1. Programming languages - C++ (англ.). ISO/IEC 14882 (23 апреля 1998). Дата обращения: 1 мая 2013. Архивировано 17 мая 2013 года.
  2. 1 2 Alexander Stepanov and Meng Lee. The Standard Template Library (англ.). HP Laboratories Technical Report 95-11(R.1) (14 ноября 1995). Дата обращения: 1 мая 2013. Архивировано 17 мая 2013 года.
  3. Draft Technical Report on C++ Library Extensions (англ.) : journal. — ISO/IEC JTC1/SC22/WG21, 2005. — 24 June. Архивировано 14 апреля 2011 года.
  4. 1 2 3 ISO/IEC 14882:2017. ISO (2 сентября 2011). Дата обращения: 2 мая 2013. Архивировано 17 мая 2013 года.
  5. Josuttis, Nicolai M. The C++ standard library : a tutorial and reference (англ.). — Addison-Wesley, 2012. — ISBN 0-321-62321-5.
  6. 1 2 3 Stroustrup, Bjarne. The C++ Programming Language: Special Edition (англ.). — Addison-Wesley, 2000. — ISBN 0-201-70073-5.
  7. 1 2 3 4 Boost Library Documentation (англ.). Дата обращения: 1 мая 2013. Архивировано 17 мая 2013 года.
  8. Boost Library Documentation : mem_fn.hpp (англ.). Дата обращения: 2 мая 2013. Архивировано 17 мая 2013 года.
  9. C++ FCD Comment Status :GB95 (англ.). Дата обращения: 3 мая 2013. Архивировано 17 мая 2013 года.
  10. Deprecating unary_function and binary_function (англ.). Дата обращения: 3 мая 2013. Архивировано 17 мая 2013 года.
  11. Deprecating unary_function and binary_function (Revision 1) (англ.). Дата обращения: 3 мая 2013. Архивировано 17 мая 2013 года.

Ссылки

править