Функции обратного вызова в C++

В C++, когда и как вы используете функцию обратного вызова?

EDIT:
Я хотел бы увидеть простой пример для написания функции обратного вызова.

9 ответов


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

что вызовов(?) и зачем их использовать(!)

обратный вызов-это вызвать (см. ниже), принятый классом или функцией, используемый для настройки текущей логики в зависимости от этого обратного вызова.

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

многие функции библиотеки стандартных алгоритмов <algorithm> использовать обратные вызовы. Например,for_each алгоритм применяет унарный обратный вызов к каждому элементу в диапазоне итераторов:

template<class InputIt, class UnaryFunction>
UnaryFunction for_each(InputIt first, InputIt last, UnaryFunction f)
{
  for (; first != last; ++first) {
    f(*first);
  }
  return f;
}

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

std::vector<double> v{ 1.0, 2.2, 4.0, 5.5, 7.2 };
double r = 4.0;
std::for_each(v.begin(), v.end(), [&](double & v) { v += r; });
std::for_each(v.begin(), v.end(), [](double v) { std::cout << v << " "; });

, который печатает

5 6.2 8 9.5 11.2

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

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

  • первый обратный вызов вызывается, если требуется значение функции и градиент на основе вектора входных значений (логический обратный вызов: определение значения функции / градиентный вывод).
  • второй обратный вызов вызывается один раз для каждого шага алгоритма и получает определенную информацию о сходимости алгоритма (уведомление обратного вызова).

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

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

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

void player_jump();
void player_crouch();

class game_core
{
    std::array<void(*)(), total_num_keys> actions;
    // 
    void key_pressed(unsigned key_id)
    {
        if(actions[key_id]) actions[key_id]();
    }
    // update keybind from menu
    void update_keybind(unsigned key_id, void(*new_action)())
    {
        actions[key_id] = new_action;
    }
};

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

game_core_instance.update_keybind(newly_selected_key, &player_jump);

и таким образом изменить поведение вызова key_pressed (которого называет player_jump) когда эта кнопка нажата в следующий раз в игре.

каковы callables в C++(11)?

посмотреть концепции C++: вызываемый на cppreference для более формального описания.

функциональность обратного вызова может быть реализована несколькими способами В C++(11), так как несколько разных вещей оказываются вызываемую*:

  • указатели функций (включая указатели на функции-члены)
  • std::function объекты
  • лямбда-выражения
  • Персонализация выражения
  • объекты функций (классы с перегруженной функцией Call operator operator())

* Примечание: указатель на элементы данных также вызывается, но функция не вызывается вообще.

несколько важных способов написать обратные вызовы подробно

  • X. 1 "запись" обратного вызова в этом сообщении означает синтаксис для объявления и имени типа обратного вызова.
  • X. 2 "вызов" обратного вызова относится к синтаксису назовите эти объекты.
  • X. 3 "использование" обратного вызова означает синтаксис при передаче аргументов функции с помощью обратного вызова.

Примечание: начиная с C++17, вызов типа f(...) можно записать как std::invoke(f, ...) который также обрабатывает указатель на случай элемента.

1. Указатели функций

указатель функции является "самым простым" (с точки зрения общности; с точки зрения читаемости, возможно, худшим) типом обратного вызова может иметь.

давайте иметь простую функцию foo:

int foo (int x) { return 2+x; }

1.1 запись указателя функции / обозначения типа

A тип указателя функции нанесена надпись

return_type (*)(parameter_type_1, parameter_type_2, parameter_type_3)
// i.e. a pointer to foo has the type:
int (*)(int)

где именованный указатель на функцию тип будет выглядеть как

return_type (* name) (parameter_type_1, parameter_type_2, parameter_type_3)

// i.e. f_int_t is a type: function pointer taking one int argument, returning int
typedef int (*f_int_t) (int); 

// foo_p is a pointer to function taking int returning int
// initialized by pointer to function foo taking int returning int
int (* foo_p)(int) = &foo; 
// can alternatively be written as 
f_int_t foo_p = &foo;

The using декларация дает нам возможность сделать вещи немного более читабельным, поскольку typedef for f_int_t также может быть написано as:

using f_int_t = int(*)(int);

где (по крайней мере для меня) становится яснее, что f_int_t - это псевдоним нового типа, и распознавание типа указателя функции также проще

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

// foobar having a callback argument named moo of type 
// pointer to function returning int taking int as its argument
int foobar (int x, int (*moo)(int));
// if f_int is the function pointer typedef from above we can also write foobar as:
int foobar (int x, f_int_t moo);

1.2 обозначение обратного вызова

обозначение вызова следует простому синтаксису вызова функции:

int foobar (int x, int (*moo)(int))
{
    return x + moo(x); // function pointer moo called using argument x
}
// analog
int foobar (int x, f_int_t moo)
{
    return x + moo(x); // function pointer moo called using argument x
}

1.3 Callback использовать обозначения и совместимость типы

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

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

 int a = 5;
 int b = foobar(a, foo); // call foobar with pointer to foo as callback
 // can also be
 int b = foobar(a, &foo); // call foobar with pointer to foo as callback

1.4 пример

функция ca будет написана, которая не зависит от того, как работает обратный вызов:

void tranform_every_int(int * v, unsigned n, int (*fp)(int))
{
  for (unsigned i = 0; i < n; ++i)
  {
    v[i] = fp(v[i]);
  }
}

где возможно, обратные вызовы могут быть

int double_int(int x) { return 2*x; }
int square_int(int x) { return x*x; }

используется как

int a[5] = {1, 2, 3, 4, 5};
tranform_every_int(&a[0], 5, double_int);
// now a == {2, 4, 6, 8, 10};
tranform_every_int(&a[0], 5, square_int);
// now a == {4, 16, 36, 64, 100};

2. Указатель на член функция

указатель на функцию-член (какой класс C) - это специальный тип (и даже более сложный) указателя функции, для которого требуется объект типа C для работы на.

struct C
{
    int y;
    int foo(int x) const { return x+y; }
};

2.1 запись указателя на функцию-член / тип обозначения

A указатель на тип функции-члена для некоторого класса T нанесена надпись

// can have more or less parameters
return_type (T::*)(parameter_type_1, parameter_type_2, parameter_type_3)
// i.e. a pointer to C::foo has the type
int (C::*) (int)

где именованный указатель на функцию-член будет-по аналогии с указателем функции-выглядеть так:

return_type (T::* name) (parameter_type_1, parameter_type_2, parameter_type_3)

// i.e. a type `f_C_int` representing a pointer to member function of `C`
// taking int returning int is:
typedef int (C::* f_C_int_t) (int x); 

// The type of C_foo_p is a pointer to member function of C taking int returning int
// Its value is initialized by a pointer to foo of C
int (C::* C_foo_p)(int) = &C::foo;
// which can also be written using the typedef:
f_C_int_t C_foo_p = &C::foo;

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

// C_foobar having an argument named moo of type pointer to member function of C
// where the callback returns int taking int as its argument
// also needs an object of type c
int C_foobar (int x, C const &c, int (C::*moo)(int));
// can equivalently declared using the typedef above:
int C_foobar (int x, C const &c, f_C_int_t moo);

2.2 запись обратного вызова

указатель на функцию-член C может быть вызван относительно объекта типа C С помощью операции доступа к члену на указатель разыменован. Примечание: В Скобках требуется!

int C_foobar (int x, C const &c, int (C::*moo)(int))
{
    return x + (c.*moo)(x); // function pointer moo called for object c using argument x
}
// analog
int C_foobar (int x, C const &c, f_C_int_t moo)
{
    return x + (c.*moo)(x); // function pointer moo called for object c using argument x
}

Примечание: если указатель на C доступен синтаксис эквивалентен (где указатель на C должен быть разыменован а):

int C_foobar_2 (int x, C const * c, int (C::*meow)(int))
{
    if (!c) return x;
    // function pointer meow called for object *c using argument x
    return x + ((*c).*meow)(x); 
}
// or equivalent:
int C_foobar_2 (int x, C const * c, int (C::*meow)(int))
{
    if (!c) return x;
    // function pointer meow called for object *c using argument x
    return x + (c->*meow)(x); 
}

2.3 обратный вызов используйте обозначения и совместимые типы

функция обратного вызова, принимающая указатель функции-члена класса T можно вызвать с помощью указателя функции-члена класса T.

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

 C my_c{2}; // aggregate initialization
 int a = 5;
 int b = C_foobar(a, my_c, &C::foo); // call C_foobar with pointer to foo as its callback

3. std::function объекты (заголовок <functional>)

The std::function class - это полиморфная оболочка функций для хранения, копирования или вызова вызываемых объектов.

3.1 написании std::function объект / обозначения типа

тип a std::function объект, хранящий вызываемый, выглядит так:

std::function<return_type(parameter_type_1, parameter_type_2, parameter_type_3)>

// i.e. using the above function declaration of foo:
std::function<int(int)> stdf_foo = &foo;
// or C::foo:
std::function<int(const C&, int)> stdf_C_foo = &C::foo;

3.2 запись обратного вызова

в класс!--46--> и operator() определено, что можно использовать для вызова его цели.

int stdf_foobar (int x, std::function<int(int)> moo)
{
    return x + moo(x); // std::function moo called
}
// or 
int stdf_C_foobar (int x, C const &c, std::function<int(C const &, int)> moo)
{
    return x + moo(c, x); // std::function moo called using c and x
}

3.3 обратный вызов используйте обозначения и совместимые типы

The std::function обратный вызов более общий, чем указатели функций или указатель на функцию-член, так как различные типы могут быть переданы и неявно преобразованы в


существует также способ c выполнения обратных вызовов: указатели функций

//Define a type for the callback signature,
//it is not necessary, but makes life easier

//Function pointer called CallbackType that takes a float
//and returns an int
typedef int (*CallbackType)(float);  


void DoWork(CallbackType callback)
{
  float variable = 0.0f;

  //Do calculations

  //Call the callback with the variable, and retrieve the
  //result
  int result = callback(variable);

  //Do something with the result
}

int SomeCallback(float variable)
{
  int result;

  //Interpret variable

  return result;
}

int main(int argc, char ** argv)
{
  //Pass in SomeCallback to the DoWork
  DoWork(&SomeCallback);
}

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

//Declaration:
typedef int (ClassName::*CallbackType)(float);

//This method performs work using an object instance
void DoWorkObject(CallbackType callback)
{
  //Class instance to invoke it through
  ClassName objectInstance;

  //Invocation
  int result = (objectInstance.*callback)(1.0f);
}

//This method performs work using an object pointer
void DoWorkPointer(CallbackType callback)
{
  //Class pointer to invoke it through
  ClassName * pointerInstance;

  //Invocation
  int result = (pointerInstance->*callback)(1.0f);
}

int main(int argc, char ** argv)
{
  //Pass in SomeCallback to the DoWork
  DoWorkObject(&ClassName::Method);
  DoWorkPointer(&ClassName::Method);
}

Скотт Мейерс приводит хороший пример:

class GameCharacter;
int defaultHealthCalc(const GameCharacter& gc);

class GameCharacter
{
public:
  typedef std::function<int (const GameCharacter&)> HealthCalcFunc;

  explicit GameCharacter(HealthCalcFunc hcf = defaultHealthCalc)
  : healthFunc(hcf)
  { }

  int healthValue() const { return healthFunc(*this); }

private:
  HealthCalcFunc healthFunc;
};

Я думаю, что пример говорит обо всем.

std::function<> - это "современный" способ написания обратных вызовов C++.


A функция обратного вызова - это метод, который передается в подпрограмму и вызывается в какой-то момент подпрограммой, в которую он передается.

Это очень полезно для создания программного обеспечения многоразовые. Например, многие API операционной системы (например, API Windows) сильно используют обратные вызовы.

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


принятый ответ очень полезен и довольно всеобъемлющ. Тем не менее, OP заявляет

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

так вот, в C++11 у вас есть std::function таким образом, нет необходимости в указателях функций и подобных вещах:

#include <functional>
#include <string>
#include <iostream>

void print_hashes(std::function<int (const std::string&)> hash_calculator) {
    std::string strings_to_hash[] = {"you", "saved", "my", "day"};
    for(auto s : strings_to_hash)
        std::cout << s << ":" << hash_calculator(s) << std::endl;    
}

int main() {
    print_hashes( [](const std::string& str) {   /** lambda expression */
        int result = 0;
        for (int i = 0; i < str.length(); i++)
            result += pow(31, i) * str.at(i);
        return result;
    });
    return 0;
}

этот пример кстати как-то реально, потому что вы хотите вызвать функцию print_hashes с различными реализациями хэш-функций, для этой цели я привел простой пример. Он получает строку, возвращает int (хэш-значение предоставленной строки), и все, что вам нужно запомнить из синтаксической части, это std::function<int (const std::string&)> который описывает такую функцию как входной аргумент функции, которая ее вызовет.


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

изменить на основе обратной связи:

несмотря на отрицательную обратную связь этот ответ получил, это не неправильно. Я постараюсь лучше объяснить, откуда я.

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

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

например, документация .NET для объекта iformatprovider говорит, что "GetFormat является методом обратного вызова", хотя это всего лишь простой метод интерфейса. Я не думаю, что кто-то будет утверждать, что все вызовы виртуальных методов являются функциями обратного вызова. Что делает GetFormat методом обратного вызова, это не механика того, как он передается или вызывается, но семантика вызывающего объекта, который будет вызывать метод GetFormat объекта.

некоторые языки включают функции с явной семантикой обратного вызова, обычно связанные с событиями и обработкой событий. Например, C# имеет событие тип с синтаксисом и семантикой явно разработанный вокруг концепции обратных вызовов. Visual Basic имеет свой ручки предложение, которое явно объявляет метод функцией обратного вызова при абстрагировании понятие делегатов или указателей функций. В этих случаях семантическое понятие обратного вызова интегрируется в сам язык.

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

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


функции обратного вызова являются частью стандарта C, следовательно, также частью c++. Но если вы работаете с C++, я бы посоветовал вам использовать шаблон Observer вместо этого:http://en.wikipedia.org/wiki/Observer_pattern


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

в C++ желательно, чтобы функции обратного вызова вызывали метод классов. При этом у вас есть доступ к данным-членам. Если вы используете способ определения обратного вызова C, вам придется указать его на статическую функцию-член. Это не очень желательно.

вот как вы можете использовать обратные вызовы в C++. Предположим 4 файлов. Ля пара .СРР./H-файлы для каждого класса. Класс C1-это класс с методом, который мы хотим перезвонить. C2 вызывает метод C1. В этом примере функция обратного вызова принимает 1 параметр, который я добавил для читателей. В Примере не показаны объекты, которые создаются и используются. Один из вариантов использования для этой реализации - когда один класс считывает и сохраняет данные во временное пространство, а другой-обрабатывает данные. С функцией обратного вызова, для каждой строки данных читать обратный вызов может обработать его. Этот метод отрезает вне накладные расходы временного требуемого космоса. Это особенно полезно для SQL-запросов, которые возвращают большой объем данных,которые затем должны быть обработаны.

/////////////////////////////////////////////////////////////////////
// C1 H file

class C1
{
    public:
    C1() {};
    ~C1() {};
    void CALLBACK F1(int i);
};

/////////////////////////////////////////////////////////////////////
// C1 CPP file

void CALLBACK C1::F1(int i)
{
// Do stuff with C1, its methods and data, and even do stuff with the passed in parameter
}

/////////////////////////////////////////////////////////////////////
// C2 H File

class C1; // Forward declaration

class C2
{
    typedef void (CALLBACK C1::* pfnCallBack)(int i);
public:
    C2() {};
    ~C2() {};

    void Fn(C1 * pThat,pfnCallBack pFn);
};

/////////////////////////////////////////////////////////////////////
// C2 CPP File

void C2::Fn(C1 * pThat,pfnCallBack pFn)
{
    // Call a non-static method in C1
    int i = 1;
    (pThat->*pFn)(i);
}

повышение по singals2 позволяет подписаться на общие функции-члены (без шаблонов!) и безопасным способом.

пример: сигналы документ-взгляда можно использовать для того чтобы снабдить гибкое Документ-вид архитектуры. Документ будет содержать сигнал который каждый из видов может подключаться. Следующий класс документов определяет простой текстовый документ, поддерживающий представления mulitple. Заметить что он хранит один сигнал, к которому все представления будут подключены.

class Document
{
public:
    typedef boost::signals2::signal<void ()>  signal_t;

public:
    Document()
    {}

    /* Connect a slot to the signal which will be emitted whenever
      text is appended to the document. */
    boost::signals2::connection connect(const signal_t::slot_type &subscriber)
    {
        return m_sig.connect(subscriber);
    }

    void append(const char* s)
    {
        m_text += s;
        m_sig();
    }

    const std::string& getText() const
    {
        return m_text;
    }

private:
    signal_t    m_sig;
    std::string m_text;
};

Далее, мы можем начать определять вид. Следующий класс TextView обеспечивает простое представление текста документа.

class TextView
{
public:
    TextView(Document& doc): m_document(doc)
    {
        m_connection = m_document.connect(boost::bind(&TextView::refresh, this));
    }

    ~TextView()
    {
        m_connection.disconnect();
    }

    void refresh() const
    {
        std::cout << "TextView: " << m_document.getText() << std::endl;
    }
private:
    Document&               m_document;
    boost::signals2::connection  m_connection;
};