Сообщений 0    Оценка 701        Оценить  
Система Orphus

Library 9. Boost.Bind

Глава из книги “Beyond the C++ Standard Library: An Introduction to Boost”

Автор: Bjorn Karlsson
Перевод: Алексей Кирюшкин
The RSDN Group

Источник: Beyond the C++ Standard Library: An Introduction to Boost
Опубликовано: 24.02.2006
Исправлено: 15.04.2009
Версия текста: 1.0
Введение
Использование
Вызов методов
Заглянем за кулисы
Дополнительно о заменителях и аргументах
Динамические критерии сортировки
Функциональная композиция, Часть I
Функциональная композиция, Часть II
Семантика значений или указателей?
Виртуальные функции также могут быть связаны
Связывание с перемеными-членами
To Bind or Not to Bind
Избавляемся от состояния
От Boost.Bind к Boost.Function
Заключение

Введение

Библиотека Boost.Bind позволяет создавать функциональные объекты и связывать их с функциями (свободными или методами классов). Вместо передачи аргументов непосредственно в функцию, они теперь могут быть переданы через созданный биндером (binder, иногда переводится как связыватель или связывающий шаблон – прим.пер.) функциональный объект, с возможностью изменить число аргументов и переупорядочить их так, как это необходимо.

Тип возвращаемого значения различных перегруженных версий функции bind не определен, так что нет никаких гарантий относительно сигнатуры возвращаемого функционального объекта. Если нужно будет сохранить где-нибудь этот функциональный объект вместо непосредственной передачи в другую функцию, вам поможет библиотека Boost.Function. Чтобы понять, что же возвращают функции bind, нужно полностью понимать, какое преобразование имеет место. Если взять в качестве примера одну из перегруженных функций bind:

template<class R, class F> unspecified-1 bind(F f);

это будет (цитируя online-документацию) "функциональный объект λ, такой, что выражение λ(v1, v2, ..., vm) эквивалентно выражению f(), неявно преобразованному к R". Таким образом, функция, связанная внутри биндера и результат последующих обращений к этому функциональному объекту приводят к возвращаемому значению (если таковое имеется), то есть к параметру R шаблона. Рассматриваемая реализация поддерживает до девяти аргументов функции.

Boost.Bind состоит из множества функций и классов, но как пользователи, мы непосредственно не используем ничего кроме одной из перегруженных версий функции bind. Все операции связывания происходят в функции bind, и можно обойтись без зависимости от типа возвращаемого значения. При использовании bind заменители (placeholders) для аргументов (_1, _2, и так далее) не нуждаются во введении в текущую область видимости с помощью using-объявления или using-директивы, т.к. находятся в анонимном пространстве имен. Таким образом, при использовании Boost.Bind редко есть повод писать одну из следующих строк:

using boost::bind;
using namespace boost;

Из недосказанного: Поиск Кёнига (Koening lookup, он же argument-dependent name lookup) в данном случае найдет bind из boost, так как тип заменителей, template<int I> class arg {}; определен в namespace boost. Прим. перев.

Как уже было упомянуто, текущая реализация Boost.Bind поддерживает девять заменителей (_1, _2, _3, и т.д.), то есть до девяти аргументов. Поучительно хотя бы просмотреть краткий обзор Boost.Bind для большего понимания того, как выполняется вывод (deduction) типов, и почему это не всегда работает. К синтаксису сигнатур указателей на методы классов и свободные функции нужно привыкнуть, но это полезный навык. Вы увидите, что есть перегрузка как свободных функций, так и функций-членов класса. Также, есть перегрузки для каждого числа аргументов. Вместо того, чтобы приводить краткий обзор здесь, я рекомендую вам документацию по Boost.Bind на www.boost.org.

Использование

Boost.Bind предлагает единообразный синтаксис для функций, функциональных объектов, и даже для семантики значений и семантики указателей. Начнем с простых примеров стандартного связывания, а затем перейдем к функциональным композициям через вложенное связывание. Один из ключей к пониманию того, как использовать bind – концепция заменителей. Заменители символизируют аргументы, которые должны передаваться результирующему функциональному объекту. Boost.Bind поддерживает до девяти таких аргументов. Заменители обозначаются как _1, _2, _3, _4, и так далее до _9, и используются в тех местах, где обычно находятся аргументы функции. В качестве первого примера определим функцию, nine_arguments, которую затем будем вызывать, используя bind:

#include <iostream>
#include "boost/bind.hpp"

void nine_arguments(
  int i1, int i2, int i3, int i4, 
  int i5, int i6, int i7, int i8, int i9) 
{
  std::cout << i1 << i2 << i3 << i4 << i5
    << i6 << i7 << i8 << i9 << '\n';
}

int main() 
{
  int i1 = 1, i2 = 2, i3 = 3, i4 = 4, i5 = 5, i6 = 6, i7 = 7, i8 = 8, i9 = 9;
  (boost::bind(&nine_arguments, _9, _2, _1, _6, _3, _8, _4, _5, _7))
    (i1, i2, i3, i4, i5, i6, i7, i8, i9);
}

В этом примере создается анонимный временный биндер и немедленно вызывается с передачей параметров его оператору вызова функции «()». Заметьте, что порядок заменителей перемешивается. Это иллюстрирует возможности изменения порядка аргументов. Обратите также внимание, что заменители могут использоваться в выражении не по одному разу. Вывод этой программы следующий:

921638457

Из примера видно, как заменители соотносятся с аргументами с номером заменителя: _1 заменяется первым аргументом, _2 – вторым аргументом, и так далее.

Далее будет показано, как вызывать методы классов.

Вызов методов

Давайте рассмотрим вызов методов с использованием bind. Сперва попробуем реализовать то же самое при помощи STL, чтобы сравнить это решение с тем, в котором используется Boost.Bind. При хранении элементов некоторого класса в контейнерах STL обычное дело – вызвать метод этого класса для некоторых или всех элементов. Это может быть сделано в цикле, и слишком часто делается именно таким образом, однако есть решения получше. Для демонстрации того, что легкость в использовании и мощь Boost.Bind действительно огромны, мы будем использовать простой класс status:

class status 
{
  std::string name_;
  bool ok_;

public:
  status(const std::string& name):name_(name), ok_(true) {}

  void break_it() 
  {
    ok_ = false;
  }

  bool is_broken() const 
  {
    return ok_;
  }

  void report() const 
  {
    std::cout << name_ << " is " 
      << (ok_ ? "working nominally":"terribly broken") << '\n';
  }
};

Если экземпляры этого класса хранятся в контейнере vector, и нужно вызвать метод report, можно соблазниться таким решением:

std::vector<status> statuses;
statuses.push_back(status("status 1"));
statuses.push_back(status("status 2"));
statuses.push_back(status("status 3"));
statuses.push_back(status("status 4"));

statuses[1].break_it();
statuses[2].break_it();

for (std::vector<status>::iterator it = statuses.begin();
  it!=statuses.end();++it) 
{
  it->report();
}

Этот цикл выполняет работу корректно, но многословно, неэффективно (из-за множественных запросов к statuses.end()) и не столь ясно, как с использованием алгоритма STL, который существует точно для этой цели – for_each. Чтобы использовать for_each для замены цикла, необходимо использовать адаптер для вызова метода report у элементов массива. В данном случае, так как элементы сохранены по значению, адаптер mem_fun_ref – то, что доктор прописал:

std::for_each(
  statuses.begin(), 
  statuses.end(), 
  std::mem_fun_ref(&status::report));

Это – правильный и нормальный способ сделать то, что нужно, весьма краткий, и без неясностей относительно того, что именно этот код делает. Эквивалентный код, использующий Boost.Bind, будет таким:

std::for_each(
  statuses.begin(), 
  statuses.end(), 
  boost::bind(&status::report, _1));

Нужно отметить, что boost::mem_fn, который тоже был принят в Library Technical Report, будет работать точно так же в тех случаях, когда нет никаких аргументов. boost::mem_fn заменяет std::mem_fun и std::mem_fun_ref.

Данная версия кода также ясна и понятна. Это первое реальное использование вышеупомянутых заменителей библиотеки Bind. И компилятору, и читателю нашего кода сообщается, что функция, вызывающая биндер, должна заменить _1 фактическим параметром. Хотя этот код действительно экономит несколько символов при наборе, в данном конкретном случае нет никакого большого различия между mem_fun_ref из STL и bind. Но давайте, используя этот же пример, изменим его так, чтобы в контейнере хранились указатели:

std::vector<status*> p_statuses;
p_statuses.push_back(new status("status 1"));
p_statuses.push_back(new status("status 2"));
p_statuses.push_back(new status("status 3"));
p_statuses.push_back(new status("status 4"));

p_statuses[1]->break_it();
p_statuses[2]->break_it();

Пока еще можно использовать STL, но уже больше нельзя использовать mem_fun_ref. Потребуется помощь адаптера mem_fun (имя которого является несколько некорректным), но то, что должно быть сделано – сделано:

std::for_each(
  p_statuses.begin(), 
  p_statuses.end(), 
  std::mem_fun(&status::report));

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

std::for_each(
  p_statuses.begin(), 
  p_statuses.end(), 
  boost::bind(&status::report, _1));

Как можно видеть, это точно тот же код, что и в предыдущем примере. Это означает, что под bind подразумевается то же, что и выше. Однако с использованием указателей появилась другая проблема, а именно – управление временем жизни. Нужно вручную освобождать элементы p_statuses, а это и чревато ошибками, и является излишеством. Можно перейти к использованию “умных” указателей и снова изменить код:

std::vector<boost::shared_ptr<status> > s_statuses;
s_statuses.push_back(boost::shared_ptr<status>(new status("status 1")));
s_statuses.push_back(boost::shared_ptr<status>(new status("status 2")));
s_statuses.push_back(boost::shared_ptr<status>(new status("status 3")));
s_statuses.push_back(boost::shared_ptr<status>(new status("status 4")));
s_statuses[1]->break_it();
s_statuses[2]->break_it();

Итак, какой адаптер из стандартной библиотеки следует использовать? mem_fun и mem_fun_ref неприменимы, так как класс “умного” указателя не имеет метода report, и, таким образом, следующий код не будет компилироваться:

std::for_each(
  s_statuses.begin(), 
  s_statuses.end(), 
  std::mem_fun(&status::report));

Таким образом, стандартная библиотека уже не может помочь в решении этой задачи.

Это станет возможным в будущем, так как и mem_fn, и bind будут частью новой стандартной библиотеки.

Следовательно, необходимо или прибегнуть к тому же самому циклу, от которого нужно было избавиться, или использовать Boost.Bind, который делает точно то, что нужно:

std::for_each(
  s_statuses.begin(), 
  s_statuses.end(), 
  boost::bind(&status::report, _1));

И снова код идентичен предыдущему примеру (кроме отличия в имени контейнера). Один и тот же синтаксис используется для связывания независимо от того, применяется семантика значений, семантика указателей, или же используются “умные” указатели. Иногда различный синтаксис помогает пониманию кода, но в данном случае задача состоит в том, чтобы вызвать метод элементов контейнера, ни более и ни менее. Значение единообразного синтаксиса нельзя недооценивать, так как это помогает человеку, который пишет код, и всем, кто позже должен будет его поддерживать (конечно, сейчас мы не пишем код, который нуждается в поддержке, но давайте притворимся, что это так).

Эти примеры продемонстрировали самый элементарный и общий случай, когда использование Boost.Bind превосходит другие способы. Стандартная библиотека действительно предлагает некоторые основные инструментальные средства, делающие то же самое. Но Boost.Bind предлагает более последовательный синтаксис и дополнительные функциональные возможности, которых в настоящее время недостает стандартной библиотеке.

Заглянем за кулисы

После начала использования Boost.Bind вы неизбежно начнете задаваться вопросом, как это все на самом деле работает. Кажется волшебством, как bind выводит типы аргументов и тип возвращаемого значения, не говоря уж о заменителях. Рассмотрим вкратце некоторые из механизмов, управляющих этим зверем. Это поможет узнать немного о том, как работает bind. Особенно полезным это может оказаться, когда вы попытаетесь декодировать чудесно краткие и ясные сообщения, которые компилятор исторгает при малейшей ошибке.

Создадим очень простой биндер, который по минимуму подражает синтаксису Boost.Bind. Чтобы избежать растягивания этого отступления на несколько страниц, реализуем только один тип связывания – для метода, принимающего единственный параметр. Кроме того, не будем вникать в подробности обработки cv-квалификации и тому подобного. Оставим только самое простое.

Прежде всего, нужно иметь возможность вывести возвращаемый тип, тип класса, и тип параметра для функции, которую мы должны связать. Сделаем это при помощи следующего шаблона функции:

template <typename R, typename T, typename Arg>
simple_bind_t<R, T, Arg> simple_bind(
  R (T::*fn)(Arg), 
  const T& t, 
  const placeholder&) 
{
  return simple_bind_t<R, T, Arg>(fn, t);
}

Приведенный код может поначалу и напугать. А что вы хотели, часть машинерии все же нужно определить. Сосредоточимся, однако, на том месте, где происходит вывод типа. Обратите внимание, что есть три параметра шаблона для функции, R, T, и Arg. R – тип возвращаемого значения, T – тип класса, и Arg – тип отдельного аргумента. Эти параметры шаблона – то, что составляет первый аргумент функции simple_bind, то есть R (T::*f) (Arg). Таким образом, передача метода с единственным формальным параметром в simple_bind позволяет компилятору выводить R как тип возвращаемого значения метода, T как класс, в котором определен метод, и Arg как тип параметра метода. Тип возвращаемого значения simple_bind – функциональный объект, который параметризуется теми же самыми типами, что и simple_bind, и чей конструктор получает указатель на метод и экземпляр класса (T). simple_bind просто игнорирует заменитель (последний аргумент функции), и причина, по которой я все-таки включил его – лишь моделирование синтаксиса Boost.Bind. В более продвинутой реализации этой концепции, очевидно, необходимо было бы использовать этот параметр, но пока можно позволить себе роскошь игнорировать его. Реализация функционального объекта довольно прямолинейна:

template <typename R, typename T, typename Arg>
class simple_bind_t 
{
  typedef R (T::*fn)(Arg);
  fn fn_;
  T t_;

public:
  simple_bind_t(fn f, const T& t) : fn_(f), t_(t) { }

  R operator()(Arg& a) 
  {
    return (t_.*fn_)(a);
  }
};

Как видно из реализации simple_bind, конструктор принимает два аргумента: первый – указатель на метод, и второй – ссылка на экземпляр объекта (тип const T), у которого будет вызываться метод. Наконец, оператор вызова функции возвращает R (тип возвращаемого значения вызываемого метода), и принимает параметр типа Arg, передаваемого в метод. Несколько замороченным может показаться синтаксис вызова метода:

(t_.*fn_)(a);

«.*» является указателем-на-член класса, используемым, когда первый операнд имеет тип class T; есть также другой оператор указателя-на-член класса, ->*, который используется, когда первый операнд - указатель на T. Осталось еще создать заменитель, то есть переменную, которая используется вместо фактического аргумента. Мы можем создать такой заменитель, используя анонимное пространство имен, содержащее переменную некоторого типа; давайте назовем ее placeholder:

namespace 
{
  class placeholder {};
  placeholder _1;
}

Создадим простой класс и маленькое приложение для проверки:

class Test 
{
public:
  void do_stuff(const std::vector<int>& v) 
  {
    std::copy(v.begin(), v.end(),  std::ostream_iterator<int>(std::cout, " "));
  }
};

int main() 
{
  Test t;
  std::vector<int> vec;
  vec.push_back(42);
  simple_bind(&Test::do_stuff, t, _1)(vec);
}

Когда мы используем simple_bind как функцию с параметрами из примера, автоматически выводятся типы; R - void, T - Test, и Arg – ссылка на const std::vector<int>. Функция возвращает экземпляр simple_bind_t<void, Test, Arg>, для которого мы немедленно вызываем оператор «()», передавая параметр vec.

При попытке скомпилировать этот пример, в строке

R operator() (Arg& a )

в классе simple_bind_t вы скорее всего получите ошибку “reference to reference is illegal", так как do_stuff() также принимает в качестве аргумента ссылку. Если оставлять этот пример столь же простым, то можно просто убрать передачу аргумента по ссылке в operator():

R operator() (Arg a )

Тем не менее существуют и общие решения, позволяющие избавиться от копирования параметров, передаваемых в оператор «()», независимо от того, будут ли связываемые функции принимать параметры по ссылке или по значению. Данная ситуация разбирается, например, в книге Андрей Александреску. Современное проектирование на С++. Там же приведена информация о том, что Бьярн Страуструп предложил Комитету по Стандартизации разрешить ссылки на ссылки и обрабатывать результирующие ссылки как одиночные ссылки. Прим.пер.

Надеюсь, что simple_bind подкинул вам идей относительно того, как работают биндеры. А сейчас пришло время вернуться к Boost.Bind!

Дополнительно о заменителях и аргументах

Первый пример продемонстрировал, что bind поддерживает до девяти аргументов, а теперь посмотрим поближе на то, как взаимодействуют аргументы и заменители. Прежде всего, нужно обратить внимание, что есть важное различие между свободными функциями и методами: при связывании метода первый аргумент bind должен быть экземпляром класса, в котором определен метод! Если проще, этот явный параметр занимает место неявного this, который передается во все нестатические методы. Прилежный читатель обратит внимание, что на практике это означает, что при связывании методов поддерживаются только (!) восемь параметров, потому что первый будет использован под текущий объект. Следующий пример определяет свободную функцию print_string и класс some_class с методом print_string, с последующим использованием их в bind-выражении:

#include <iostream>
#include <string>
#include "boost/bind.hpp"

class some_class 
{
public:
  typedef void result_type;

  void print_string(const std::string& s) const 
  {
    std::cout << s << '\n';
  }
};

void print_string(const std::string s) 
{
  std::cout << s << '\n';
}

int main() 
{
  (boost::bind(&print_string, _1))("Hello func!");
  some_class sc;
  (boost::bind(&some_class::print_string, _1, _2))
    (sc, "Hello member!");
}

Первое выражение bind осуществляет связывание со свободной функцией print_string. Поскольку функция ожидает один параметр, мы должны использовать одну метку-заменитель (_1), чтобы сказать bind, какой из ее параметров будут передавать как первый параметр print_string. Чтобы вызывать результирующий функциональный объект, мы должны передать параметр string в оператор «()». Поскольку аргументом является ссылка на const std::string, передача литеральной строки вызывает обращение к соответствующему конструктору std::string.

(boost::bind(&print_string, _1))("Hello func!");

Второй биндер адаптирует метод print_string класса some_class. Первый аргумент bind – указатель на метод. Однако указатель на нестатический метод - не настоящий указатель.

ПРИМЕЧАНИЕ

Да, я знаю, что это звучит странно. Тем не менее, это так.

Вызвать метод можно только при наличии объекта. Именно поэтому bind-выражение должно обозначить два аргумента биндера, оба из которых должны быть предоставлены при вызове.

boost::bind(&some_class::print_string, _1, _2);

Чтобы прочувствовать смысл этого выражения, рассмотрим, как может быть использован результирующий функциональный объект. Мы должны передать ему экземпляр some_class и параметр для print_string:

(boost::bind(&some_class::print_string, _1, _2))(sc, "Hello member!");

Первый параметр в операторе вызова функции – this, то есть экземпляр some_class. Обратите внимание, что первый параметр может быть указателем (“умным” или не очень) или ссылкой на экземпляр; bind очень хорошо приспосабливается. Второй аргумент в операторе «()» – первый аргумент метода. В данном случае мы "задержали" оба параметра, то есть мы определили биндер так, что он ожидает получить и объект, и аргумент метода через оператор вызова функции. Однако не обязательно поступать именно так. Например, можно создать биндер, вызывающий print_string для объекта, создаваемого каждый раз в момент вызова:

(boost::bind(&some_class::print_string, some_class(), _1))("Hello member!");

Результирующий функциональный объект уже содержит экземпляр some_class, так что нужен только один заменитель (_1) и один параметр для оператора вызова функции. Наконец, можно также создать так называемый nullary функциональный объект, также связывая и строку:

(boost::bind(&some_class::print_string,  some_class(), "Hello member!"))();

Эти примеры ясно показывают многосторонность bind. Можно пользоваться этой особенностью, чтобы кэшировать значения аргументов, нужных связываемой функции. Можно также переупорядочивать аргументы так, как вам вздумается. Далее, мы рассмотрим, как использовать bind для создания предикатов сортировки на лету.

Динамические критерии сортировки

При сортировке элементов контейнера встречаются ситуации, когда нужно создавать функциональный объект, определяющий критерий сортировки. Это необходимо, если не определены операторы сравнения, или если существующие операторы сравнения не определяют нужный критерий сортировки. Иногда возможно использование функциональных объектов сравнения из стандартной библиотеки (std::greater, std::greater_equal, и т.д), но при этом можно использовать только заранее определенные для типов операторы сравнения, невозможно задать нужные критерии в месте вызова. Используем класс personal_info, чтобы продемонстрировать возможности Boost.Bind.

personal_info содержит имя, фамилию, и возраст, и не предоставляет никаких операторов сравнения. Информация является неизменной после создания, и может быть найдена с использованием методов name(), surname() и age().

class personal_info 
{
  std::string name_;
  std::string surname_;
  unsigned int age_;

public:
  personal_info(
    const std::string& n, 
    const std::string& s, 
    unsigned int age):name_(n), surname_(s), age_(age) {}

  std::string name() const
  {
    return name_;
  }

  std::string surname() const
  {
    return surname_;
  }

  unsigned int age() const
  {
    return age_;
  }
};

Обеспечим возможность вывода класса в поток при помощи следующего оператора:

std::ostream& operator <<(std::ostream& os, const personal_info& pi)
{
  os << pi.name() << ' ' << pi.surname() << ' ' << pi.age() << '\n';
  return os;
}

Если мы захотим отсортировать контейнер с элементами типа personal_info, понадобится соответствующий предикат. Почему же мы сразу не добавили в personal_info операторы сравнения? Одна из причин состоит в том, что возможно несколько вариантов сортировки, и неизвестно, какой из них будет востребован пользователями. Можно было бы добавить различные методы для разных критериев сортировки, но это потребует кодирования всех возможных критериев сортировки в классе, что не всегда возможно. К счастью, очень просто создать предикат непосредственно в месте вызова, используя bind. Допустим, что нужна сортировка по возрасту (доступному через метод age()). Можно было бы создать функциональный объект только для этого критерия:

class personal_info_age_less_than :
  public std::binary_function<personal_info, personal_info, bool> 
{
public:
  bool operator()(const personal_info& p1, const personal_info& p2) 
  {
     return p1.age() < p2.age();
  }
};

Предикат personal_info_age_less_than сделан адаптируемым при помощи публичного наследования от binary_function. Оно обеспечивает соответствующие typedef-ы, необходимые при использовании personal_info_age_less_than совместно, например, с std::not2. Если бы vector vec содержал элементы типа personal_info, можно было бы использовать функциональный объект, например, так:

std::sort(vec.begin(), vec.end(), personal_info_age_less_than());

Это прекрасно работает, пока число различных вариантов сравнений ограничено. Существует, однако, и потенциальная проблема – так как логика сравнения определена в отдельном месте, это может сделать код тяжелее для понимания. С длинным и подробным именем, типа того, что мы выбрали, не должно быть проблем, но не все случаи настолько четки, и есть реальный шанс, что нам пришлось бы создавать множество функциональных объектов для “больше чем”, “меньше или равно” и так далее.

Так может Boost.Bind нам поможет? Да, причем три раза. Если мы исследуем текущую проблему, то обнаружим, что есть три вещи, которые нужно сделать. Во-первых, связать логическую операцию, типа std::less. Это просто, и дает нам первую часть кода.

boost::bind<bool>(std::less<unsigned int>(), _1, _2);

Обратите внимание, что мы явно указываем тип возвращаемого значения, подставляя параметр bool для bind. Это иногда необходимо на несоответствующих стандарту компиляторах или в контекстах, где тип возвращаемого значения не может быть выведен. Если функциональный объект содержит typedef для result_type, нет никакой необходимости явно указывать тип возвращаемого значения.

Все функциональные объекты стандартной библиотеки имеют определенный result_type, так что они работают с механизмом вывода типа возвращаемого значения bind.

Итак, у нас есть функциональный объект, который принимает два параметра, оба типа unsigned int. Но мы пока не можем его использовать, так как элементы имеют тип personal_info, и нужно еще извлечь age из этих элементов и передать эти age как параметры в std::less. И опять мы можем использовать bind:

boost::bind(
  std::less<unsigned int>(), 
  boost::bind(&personal_info::age, _1), 
  boost::bind(&personal_info::age, _2));

В этом фрагменте создаются еще два биндера. Первый вызывает personal_info::age с первым параметром (_1) главного биндера в качестве аргумента. Второй вызывает personal_info::age со вторым параметром (_2) главного биндера в качестве аргумента. Поскольку std::sort передает два экземпляра personal_info в оператор () главного биндера, в результате должны быть вызваны personal_info::age() для двух объектов personal_info из сортируемого vector-а. И наконец, главный биндер передает возраст, возвращенный операторами () двух новых, внутренних биндеров, в std::less. Что и требовалось получить! Результат вызова этого функционального объекта – результат std::less, что означает, что мы имеем правильный функциональный объект сравнения, легко используемый для сортировки контейнера с объектами personal_info. Вот как это выглядит в действии:

std::vector<personal_info> vec;
vec.push_back(personal_info("Little", "John", 30));
vec.push_back(personal_info("Friar", "Tuck", 50));
vec.push_back(personal_info("Robin", "Hood", 40));

std::sort(
  vec.begin(), 
  vec.end(), 
  boost::bind(
    std::less<unsigned int>(), 
    boost::bind(&personal_info::age, _1), 
    boost::bind(&personal_info::age, _2)));

Можно сделать другую сортировку, просто связывая другой член (переменную или функцию) personal_info. Например, фамилию:

std::sort(
  vec.begin(), 
  vec.end(), 
  boost::bind(
    std::less<std::string>(), 
    boost::bind(&personal_info::surname, _1), 
    boost::bind(&personal_info::surname, _2)));

Это замечательная технология, так как она позволяет вставлять простую функциональность непосредственно в месте вызова. Код становится простым для понимания и поддержки. Технически возможно использовать для сортировки биндеры, созданные на основе сложных критериев, но это не будет мудрым решением. Добавление большего количества логики к bind-выражениям приводит к потере ясности и краткости. Хоть это и соблазнительно, пытаться сделать как можно больше на основе связывания, не нужно писать биндеры, которые будут умнее людей, которые должны будут поддерживать этот код.

Функциональная композиция, Часть I

Одна из часто встречающихся проблем состоит в том, чтобы составить функциональный объект из других функций или функциональных объектов. Предположим, что нужно проверить значение типа int, чтобы выяснить, больше ли оно, чем 5, и меньше или равно 10. Используя "обычный" код, можно написать что-то типа:

if (i > 5 && i <= 10) 
{
  // Какие-то действия
}

При обработке элементов контейнера приведенный код работает, только если поместить его в отдельную функцию. Если это нежелательно, выразить ту же функциональность можно с использованием вложенных bind (обратите внимание, что это типичный случай, когда невозможно использование bind1st и bind2nd из стандартной библиотеки). Проанализировав проблему, мы обнаружим, что необходимы операции “логического и” (std::logical_and), “больше” (std::greater) и “меньше или равно” (std::less_equal). “Логическое И” будет выглядеть как-то так:

boost::bind(std::logical_and<bool>(), _1, _2);

Далее, нужен предикат, отвечающий на вопрос, больше ли _1, чем 5:

boost::bind(std::greater<int>(), _1, 5);

И другой предикат, который отвечает, меньше или равно _1, чем 10:

boost::bind(std::less_equal<int>(), _1, 10);

Наконец, необходимо применить к ним “Логическое И”:

boost::bind(
  std::logical_and<bool>(), 
  boost::bind(std::greater<int>(), _1, 5), 
  boost::bind(std::less_equal<int>(), _1, 10));

Вложенный bind типа этого относительно просто понять, хотя он имеет постфиксный порядок. Можно прочитать код практически буквально и понять его смысл. Давайте испытаем этот биндер в следующем примере:

std::vector<int> ints;

ints.push_back(7);
ints.push_back(4);
ints.push_back(12);
ints.push_back(10);

int count=std::count_if(
  ints.begin(), 
  ints.end(), 
  boost::bind(
    std::logical_and<bool>(), 
    boost::bind(std::greater<int>(), _1, 5), 
    boost::bind(std::less_equal<int>(), _1, 10)));

std::cout << count << '\n';

std::vector<int>::iterator int_it=std::find_if(
  ints.begin(), 
  ints.end(), 
  boost::bind(std::logical_and<bool>(), 
    boost::bind(std::greater<int>(), _1, 5), 
    boost::bind(std::less_equal<int>(), _1, 10)));

if (int_it!=ints.end()) {
  std::cout << *int_it << '\n';
}

При использовании вложенных bind важно тщательно выравнивать код должным образом – если пренебречь отступами, код может быстро стать сложным для понимания. Рассмотрите предыдущий прозрачный код, а затем посмотрите на следующий запутанный пример:

std::vector<int>::iterator int_it=
  std::find_if(ints.begin(), ints.end(), 
    boost::bind<bool>(
    std::logical_and<bool>(), 
    boost::bind<bool>(std::greater<int>(), _1, 5), 
      boost::bind<bool>(std::less_equal<int>(), _1, 10)));

Конечно, это обычная проблема длинных строк, но она становится очевидной при использовании конструкций типа приведенных, когда длинные инструкции – правило, а не исключение. Так что, пожалуйста, будьте вежливы по отношению к другим программистам, удостоверьтесь, что ваши строки переносятся по словам так, что это облегчает их чтение.

Один из трудолюбивых рецензентов этой книги спросил, почему в предыдущем примере были созданы два эквивалентных биндера, и не будет ли целесообразно создать объект биндера и использовать его два раза. Ответ состоит в том, что, так как мы не можем знать точный тип биндера (это зависит от реализации), он создается при вызове bind, и нет никакого способа объявить переменную для него. Тип также обычно очень сложен, его сигнатура включает всю информацию о типе, которая была автоматически выведена в функции bind. Есть, однако, возможность хранить результирующие функциональные объекты, используя другие средства, например из Boost.Function.

Приведенная композиция соответствует популярному расширению стандартной библиотеки, а именно функции compose2 из SGI STL, также известной как compose_f_gx_hx из библиотеки Boost.Compose (ныне устаревшей).

ПРИМЕЧАНИЕ

…и исключенной из Boost – см. http://www.boost.org/libs/compose/ - прим.перев.

Функциональная композиция, Часть II

Другая полезная функциональная композиция известна как compose1 в SGI STL, и compose_f_gx в Boost.Compose. Они предлагают способ вызвать две функции с некоторым аргументом, и передать результат самой внутренней функции в первую функцию. Пример иногда говорит больше, чем тысяча слов, так что представьте сценарий, где нужно выполнить две арифметических операции с контейнерными элементами типа double. Мы сначала добавляем 10% к значениям, и затем уменьшаем значения на 10%; пример мог бы также послужить полезным уроком для многих людей, работающих в финансовом секторе:

std::list<double> values;
values.push_back(10.0);
values.push_back(100.0);
values.push_back(1000.0);

std::transform(
  values.begin(), 
  values.end(), 
  values.begin(), 
  boost::bind(
    std::multiplies<double>(), 0.90, 
    boost::bind<double>(std::multiplies<double>(), _1, 1.10)));

std::copy(
  values.begin(), 
  values.end(), 
  std::ostream_iterator<double>(std::cout, " "));

Знаете, какой из вложенных bind будет вызван первым? Как вы, вероятно, уже поняли, первым выполнится самый внутренний bind. Это означает, что можно было бы записать эквивалентный код несколько по-другому:

std::transform(
  values.begin(), 
  values.end(), 
  values.begin(), 
  boost::bind<double>(std::multiplies<double>(), 
    boost::bind<double>(std::multiplies<double>(), _1, 1.10), 0.90));

Мы изменили порядок аргументов, передаваемых в первый bind, поменяв местами 0.90 и вложенный bind. Я и не рекомендую поступать таким образом, но в данном случае это полезно, чтобы понять, как параметры передаются в bind-функции.

Семантика значений или указателей?

Когда мы передаем экземпляр некоторого типа в bind-выражение, он будет скопирован, если только мы явно не скажем bind не копировать его. В зависимости от того, что мы делаем, это может иметь жизненное значение. Чтобы посмотреть, что происходит за кулисами, создадим класс tracer, который покажет, когда будут вызываться конструктор по умолчанию, копирующий конструктор, оператор присваивания и деструктор. Это позволит легко увидеть, какое воздействие различные варианты использования bind оказывают на передаваемые экземпляры объектов. Вот полный код класса tracer:

class tracer 
{
public:
  tracer() 
  {
    std::cout << "tracer::tracer()\n";
  }

  tracer(const tracer& other) 
  {
    std::cout << "tracer::tracer(const tracer& other)\n";
  }

  tracer& operator=(const tracer& other) 
  {
    std::cout << "tracer& tracer::operator=(const tracer& other)\n";
    return *this;
  }

  ~tracer() 
  {
    std::cout << "tracer::~tracer()\n";
  }

  void print(const std::string& s) const 
  {
    std::cout << s << '\n';
  }
};

Используем класс tracer в обычном bind-выражении:

tracer t;
boost::bind(&tracer::print, t, _1)
  (std::string("I'm called on a copy of t\n"));

Исполнение этого кода дает следующую трассировку, ясно показывающую, что задействовано копирование объектов:

tracer::tracer()
tracer::tracer(const tracer& other)
tracer::tracer(const tracer& other)
tracer::tracer(const tracer& other)
tracer::~tracer()
tracer::tracer(const tracer& other)
tracer::~tracer()
tracer::~tracer()
I'm called on a copy of t

tracer::~tracer()

При использовании объектов, для которых копирование было бы дорогим удовольствием, мы, вероятно, не смогли бы позволить себе применить bind таким образом. Однако у копирования есть и преимущества. В этом случае, например, bind-выражение и его результат не зависят от времени жизни оригинального объекта (в данном случае t), что часто является требуемым поведением. Чтобы избежать создания копий, мы должны сказать bind, что намереваемся передать объект по ссылке. Это можно сделать при помощи boost::ref и boost::cref (для ссылки и ссылки на const, соответственно), которые также являются частью библиотеки Boost.Bind. Применим boost::ref к нашему классу tracer следующим образом:

tracer t;
boost::bind(&tracer::print, boost::ref(t), _1)(
  std::string("I'm called directly on t\n"));

При выполнении этот код выдаст следующее:

tracer::tracer()
I'm called directly on t
tracer::~tracer

Это то, что нужно, чтобы избежать лишнего копирования. bind-выражение использует оригинальный экземпляр объекта, что означает, что нет никаких копий объекта tracer. Это также означает, что биндер теперь зависит от времени жизни исходного объекта tracer. Есть и другой способ избежать создания копий – передайте аргумент как указатель, а не по значению:

tracer t;
boost::bind(&tracer::print, &t, _1)(
  std::string("I'm called directly on t\n"));

Виртуальные функции также могут быть связаны

Мы уже видели, как bind может работать со свободными функциями и не виртуальными методами, но конечно, можно также связать и виртуальный метод класса. Виртуальные методы используются с Boost.Bind так же, как и не виртуальные, только связывание происходит с методом базового класса, в котором и объявлен этот метод. Это делает возможным использование биндера со всеми производными типами. Если вы осуществляете связывание непосредственно с производным типом, то тем самым ограничиваете перечень классов, с которыми может использоваться этот биндер.

ПРИМЕЧАНИЕ

Можно провести аналогию с объявлением указателя на класс при вызове виртуальной функции. Чем более производный тип указан, тем меньше классов могут быть привязаны к этому указателю.

Рассмотрим следующие классы с именами base и derived:

class base 
{
public:
  virtual void print() const 
  {
    std::cout << "I am base.\n";
  }

  virtual ~base() {}
};

class derived : public base 
{
public:
  void print() const 
  {
    std::cout << "I am derived.\n";
  }
};

Используя эти классы, можно протестировать связывание виртуальных функций, например, так:

derived d;
base b;
boost::bind(&base::print, _1)(b);
boost::bind(&base::print, _1)(d);

Исполнение этого кода ясно показывает, что все работает, как и ожидалось:

I am base.
I am derived.

Факт поддержки связывания виртуальных функций не должен был вызвать у вас удивления, но теперь мы продемонстрировали, что все работает точно так же как и для других функций.

Что случится при связывании метода, который позже переопределен производным классом, или виртуальной функции, которая объявлена как public в основном классе, но private в производном? Bind все еще будет работать? Если да, то какого поведения при этом нужно ожидать? OK, это поведение не зависит от того, используете вы Boost.Bind или нет.

Если вы связываете функцию, которая переопределяется в другом классе, функция не виртуальная, а производный класс добавляет метод с идентичной сигнатурой, будет вызвана версия базового класса. Если функция скрыта, биндер все равно может быть вызван, так как явно получает доступ к функции в базовом классе, который работает даже для скрытых методов.

Наконец, если виртуальная функция объявлена как public в базовом классе, но private в производном, вызов функции для экземпляра производного класса проходит, так как доступ происходит через базовый класс, где она public. Естественно, такой случай говорит о серьезных проблемах с дизайном.

Связывание с перемеными-членами

Бывают случаи, когда предпочтительнее передавать в bind поля, а не методы. Например, при использовании std::map или std::multimap тип элемента - std::pair<key const, data>, но информация, которую нужно использовать, часто не ключ, а данные. Предположим, необходимо передать данные каждого элемента map в функцию, которая принимает единственный параметр типа data. Нужно создать биндер для передачи члена second каждого элемента (std::pair) в связанную функцию. Вот код, который показывает, как это сделать:

void print_string(const std::string& s) 
{
  std::cout << s << '\n';
}

std::map<int, std::string> my_map;
my_map[0] = "Boost";
my_map[1] = "Bind";

std::for_each(
  my_map.begin(), 
  my_map.end(), 
    boost::bind(&print_string, boost::bind(
      &std::map<int, std::string>::value_type::second, _1)));

Вы можете использовать bind с полем точно так же, как с методом или свободной функцией. Нужно отметить, что хорошей идеей будет использовать короткие и удобные имена, чтобы сделать код более легким для чтения (и написания). В приведенном примере использование typedef для std::map улучшает удобочитаемость:

typedef std::map<int, std::string> map_type;
boost::bind(&map_type::value_type::second, _1)));

Хотя потребность использовать bind с полями возникает не так часто как с методами, этот способ остается очень удобным. Пользователи SGI STL (и производных от него), вероятно, знакомы с функциями select2nd и select1st. Они используются, чтобы выбрать first или second из std::pair, т.е. сделать то же самое, что мы сделали в этом примере. То, что bind работает с произвольными типами и произвольными именами, является решающим преимуществом.

To Bind or Not to Bind

Большая гибкость, предоставляемая библиотекой Boost.Bind одновременно является вызовом для программиста. Иногда очень соблазнительно использовать биндер, хотя обоснованным решением было бы применение отдельного функционального объекта. При помощи bind можно решить множество задач, но важно не перегнуть палку, и остановиться раньше, чем код станет трудным для чтения, понимания и поддержки. К сожалению, определить этот момент могут только сами программисты, которые совместно используют код (читая, поддерживая и расширяя), поскольку их опыт должен диктовать, что приемлемо, а что нет.

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

Например, если приходится создавать bind, настолько вложенный, что появляются сложности с пониманием, возможно, вы зашли слишком далеко. Позвольте мне объяснить это на примере:

#include <iostream>
#include <string>
#include <map>
#include <vector>
#include <algorithm>
#include "boost/bind.hpp"

void print(std::ostream* os, int i) 
{
  (*os) << i << '\n';
}

int main() 
{
  std::map<std::string, std::vector<int> > m;

  m["Strange?"].push_back(1);
  m["Strange?"].push_back(2);
  m["Strange?"].push_back(3);
  m["Weird?"].push_back(4);
  m["Weird?"].push_back(5);

  std::for_each(m.begin(), m.end(), 
    boost::bind(&print, &std::cout, 
    boost::bind(&std::vector<int>::size, 
    boost::bind(
      &std::map<std::string, 
        std::vector<int> >::value_type::second, _1))));
}

Итак, что делает приведенный код? Есть люди, которые бегло читают код, подобный этому [Hello, Peter Dimov!], но для нас, простых смертных, требуется некоторое время, чтобы выяснить, что происходит. Да, приведенный биндер вызывает метод size для second из std::map<std::string, std::vector<int> >::value_type. В случаях подобных этому, где проблема проста, но с трудом выражается с помощью биндеров, часто имеет смысл создать маленький функциональный объект вместо составного биндера, так как некоторые люди будут определенно иметь трудности с его пониманием. Простой функциональный объект, который делает то же самое, мог бы выглядеть так:

class print_size 
{
  std::ostream& os_;
  typedef std::map<std::string, std::vector<int> > map_type;

public:
  print_size(std::ostream& os):os_(os) { }

  void operator()(
    const map_type::value_type& x) const 
  {
      os_ << x.second.size() << '\n';
  }
};

Дополнительное преимущество использования функционального объекта еще и в том, что его имя говорит само за себя:

std::for_each(m.begin(), m.end(), print_size(std::cout));

Сравните этот вариант с версией, использующей биндер:

std::for_each(m.begin(), m.end(), 
  boost::bind(&print, &std::cout, 
  boost::bind(&std::vector<int>::size, 
  boost::bind(
    &std::map<std::string, 
      std::vector<int> >::value_type::second, _1))));

Или, если мы были немного более ответственны и создали typedef-ы для vector и map:

std::for_each(m.begin(), m.end(), 
  boost::bind(&print, &std::cout, 
  boost::bind(&vec_type::size, 
  boost::bind(&map_type::value_type::second, _1))));

Получилось несколько проще для понимания, но все равно многовато.

Можно найти неплохие аргументы в пользу использования bind-версии. Однако я думаю, что все и так ясно. Биндеры – невероятно эффективное инструментальное средство, которое должно использоваться ответственно, там, где оно полезно. В этом отношении использование биндеров сходно с использованием контейнеров и алгоритмов STL. Если их использование усложняет результат, используйте старый, проверенный путь.

Избавляемся от состояния

Существует несколько вариантов создания функционального объекта подобного print_size. Версия, созданная в предыдущем разделе, сохраняла ссылку на std::ostream, и использовала её, чтобы печатать возвращаемое значение функции size поля second из map_type::value_type. Вот еще раз оригинальная print_size:

class print_size 
{
  std::ostream& os_;
  typedef std::map<std::string, std::vector<int> > map_type;

public:
   print_size(std::ostream& os) : os_(os) {}

  void operator()(
    const map_type::value_type& x) const 
  {
      os_ << x.second.size() << '\n';
  }
};

Важно то, что этот класс имеет состояние, так как сохраняет std::ostream. Можно было бы избавиться от состояния, добавив ostream в список аргументов оператора вызова функции:

class print_size 
{
  typedef std::map<std::string, std::vector<int> > map_type;

public:
  typedef void result_type;
  result_type operator()(std::ostream& os, 
    const map_type::value_type& x) const 
  {
      os << x.second.size() << '\n';
  }
};

Обратите внимание, что эта версия print_size хорошо ведет себя при использовании с bind благодаря добавлению определения типа result_type. Это освобождает пользователей от необходимости явно задавать тип возвращаемого функционального объекта при использовании bind. В новой версии print_size, пользователи должны передать ostream как параметр при вызове. При использовании биндеров это просто. Изменение примера из предыдущего раздела под новую print_size даст следующее:

#include <iostream>
#include <string>
#include <map>
#include <vector>
#include <algorithm>
#include "boost/bind.hpp"

// Definition of print_size omitted

int main() 
{
  typedef std::map<std::string, std::vector<int> > map_type;

  map_type m;
  m["Strange?"].push_back(1);
  m["Strange?"].push_back(2);
  m["Strange?"].push_back(3);
  m["Weird?"].push_back(4);
  m["Weird?"].push_back(5);

  std::for_each(m.begin(), m.end(), 
    boost::bind(print_size(), boost::ref(std::cout), _1));
}

Прилежный читатель мог бы задаться вопросом, почему бы теперь не сделать print_size свободной функцией, раз уж мы избавились от состояния? Реально она может быть такой:

void print_size(std::ostream& os, 
  const std::map<std::string, std::vector<int> >::value_type& x) 
{
  os << x.second.size() << '\n';
}

Однако рассмотрим некоторые возможные обобщения. Текущая версия print_size требует, чтобы второй параметр оператора «()» был ссылкой на const std::map<std::string, std::vector<int> >, что не так часто встречается. Можно поступить лучше и параметризовать оператор «()». Это позволит использовать print_size с любым параметром, имеющим public поле second, которое, в свою очередь, содержит метод size. Вот улучшенная версия:

class print_size 
{
public:
  typedef void result_type;

  template <typename Pair> result_type operator()
    (std::ostream& os, const Pair& x) const 
  {
    os << x.second.size() << '\n';
  }
};

Этот вариант можно использовать точно так же, как предыдущий, но он гораздо гибче. Данный вид обобщения приобретает особое значение при создании функционального объекта, который может использоваться в bind-выражении. Поскольку число случаев, когда могут использоваться функциональные объекты, явно велико, большинство любых потенциальных обобщений заслуживают внимания. Двигаясь в том же направлении, мы могли бы сделать еще одно изменение, чтобы еще больше ослабить требования для типов, которые используются с print_size. Текущая версия print_size требует, чтобы второй параметр оператора «()» был подобен pair, то есть был бы объектом с полем c именем second. Если мы решим требовать только, чтобы параметр содержал метод size, функциональный объект действительно начнет оправдывать свое название:

class print_size 
{
public:
  typedef void result_type;

  template <typename T> void operator()(std::ostream& os, const T& x) const
  {
    os << x.size() << '\n';
  }
};

print_size полностью соответствует своему названию, но от пользователя в рассматриваемой ситуации потребуются большие усилия. Теперь необходимо "ручное" связывания map_type::value_type::second:

std::for_each(m.begin(), m.end(), 
  boost::bind(print_size(), boost::ref(std::cout), 
    boost::bind(&map_type::value_type::second, _1)));

Такова плата - использование bind-обобщений может вступить в противоречие с удобством в применении. Если бы мы пошли еще дальше и сняли бы даже требование наличия метода size, мы завершили бы круг и вернулись к тому, с чего начали – к bind-выражению, достаточно сложному для большинства программистов:

std::for_each(m.begin(), m.end(), 
  boost::bind(&print, &std::cout, 
  boost::bind(&vec_type::size, 
  boost::bind(&map_type::value_type::second, _1))));

Без использования lambda-средств, очевидно, также потребовалась бы функция print.

От Boost.Bind к Boost.Function

Хотя материал, рассматриваемый в этой главе, практически исчерпан, есть еще очень полезная взаимосвязь между Boost.Bind и другой библиотекой, Boost.Function, которая предоставляет дополнительные функциональные возможности. Как можно заметить, нет никакого очевидного способа сохранить биндеры для дальнейшего использования. Известно только, что это совместимые с некоторой неизвестной сигнатурой функциональные объекты. Но сохранение функционального объекта для последующего вызова – это то, что делает библиотека Boost.Function, и благодаря совместимости ее с Boost.Bind, можно присвоить ссылки на биндеры функциям, сохраняя их для отложенного обращения. Это чрезвычайно полезная концепция, которая допускает адаптирование и способствует ослаблению связей.

Заключение

Используйте Bind, если:

Обобщенный биндер – чрезвычайно полезный инструмент, когда он способствует созданию краткого, прозрачного кода. Он уменьшает число маленьких функциональных объектов, создаваемых для адаптирования функций/функциональных объектов и комбинаций функций. Хотя стандартная библиотека уже предлагает некоторую часть функциональных возможностей, предоставляемых Boost.Bind, есть существенные усовершенствования, делающие Boost.Bind лучшим выбором в большинстве случаев. В дополнение к упрощению использования уже существующих (в STL) возможностей, Boost.Bind также предлагает мощные средства функциональной композиции, дающие программисту значительные возможности без отрицательных эффектов при сопровождении. Если вы уже потратили время на изучение bind1st, bind2nd, ptr_fun, mem_fun_ref, и т.д, у вас будет минимум проблем при переходе на Boost.Bind. Если же вы только собираетесь начать использовать возможности биндинга из стандартной библиотеки C++, я настоятельно рекомендую использовать вместо них сразу Boost.Bind, так как он проще для изучения и предоставляет больше возможностей.

Я знаю множество программистов, которым не помешало бы приобщиться к чудесам биндеров вообще, и функциональной композиции в частности. Если вы являетесь одним из них, я надеюсь, что эта глава сумела продемонстрировать часть огромной мощности, порожденной соответствующей идеей. Подумайте также о значении этого типа функций, объявленных и определенных в месте вызова, для поддержки и сопровождения. Это будет легкий ветерок по сравнению с дисперсией кода, которая легко может быть вызвана маленькими, невинно выглядящими (но не являющимися таковыми), функциональными объектами, разбросанными вокруг классов только для того, чтобы обеспечить правильную сигнатуру и выполнить тривиальную задачу.

Библиотека Boost.Bind создана и поддерживается Петром Димовым (Peter Dimov), который помимо создания совершенного инструмента для связывания и функциональной композиции, сумел также добиться его безупречной работы с большинством компиляторов.


Любой из материалов, опубликованных на этом сервере, не может быть воспроизведен в какой бы то ни было форме и какими бы то ни было средствами без письменного разрешения владельцев авторских прав.
    Сообщений 0    Оценка 701        Оценить