HiveBrain v1.2.0
Get Started
← Back to all entries
patterncppCritical

Callback functions in C++

Submitted by: @import:stackoverflow-api··
0
Viewed 0 times
callbackfunctionsstackoverflow

Problem

In C++, when and how do you use a callback function?

Also, how do you write one?

Solution

Note: Most of the answers cover function pointers which is one possibility to achieve "callback" logic in C++, but as of today not the most favourable one I think.
What are callbacks(?) and why to use them(!)

A callback is a callable (see further down) accepted by a class or function, used to customize the current logic depending on that callback.

One reason to use callbacks is to write generic code which is independent of the logic in the called function and can be reused with different callbacks.

Many functions of the standard algorithms library ` use callbacks. For example, the for_each algorithm applies a unary callback to every item in a range of iterators:

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


which can be used to first increment and then print a vector by passing appropriate callables for example:

std::vector 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 << " "; });


which prints

5 6.2 8 9.5 11.2


Another application of callbacks is the notification of callers of certain events which enables a certain amount of static / compile time flexibility.

Personally, I use a local optimization library that uses two different callbacks:

  • The first callback is called if a function value and the gradient based on a vector of input values are required (logic callback: function value determination/gradient derivation).



  • The second callback is called once for each algorithm step and receives certain information about the convergence of the algorithm (notification callback).



Thus, the library designer is not in charge of deciding what happens with the information that is given to the programmer
via the notification callback and he needn't worry about how to actually determine function values because they're provided by the logic callback. Getting those things right is a task due to the library user and keeps the library slim and more generic.

Furthermore, callbacks can enable dynamic runtime behaviour.

Imagine some kind of game engine class which has a function that is fired, each time the user presses a button on his keyboard and a set of functions that control your game behaviour.
With callbacks, you can (re)decide at runtime which action will be taken.

void player_jump();
void player_crouch();

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


Here the function
key_pressed uses the callbacks stored in actions to obtain the desired behaviour when a certain key is pressed.
If the player chooses to change the button for jumping, the engine can call

game_core_instance.update_keybind(newly_selected_key, &player_jump);


and thus change the behaviour of a call to
key_pressed (which calls player_jump) once this button is pressed the next time ingame.
What are callables in C++(11)?

See C++ concepts: Callable on cppreference for a more formal description.

Callback functionality can be realized in several ways in C++(11) since several different things turn out to be callable*:

  • Function pointers (including pointers to member functions)



  • std::function objects



  • Lambda expressions



  • Bind expressions



  • Function objects (classes with overloaded function call operator operator())



* Note: Pointer to data members are callable as well but no function is called at all.
Several important ways to write callbacks in detail

  • X.1 "Writing" a callback in this post means the syntax to declare and name the callback type.



  • X.2 "Calling" a callback refers to the syntax to call those objects.



  • X.3 "Using" a callback means the syntax when passing arguments to a function using a callback.



Note: As of C++17, a call like
f(...) can be written as std::invoke(f, ...) which also handles the pointer to member case.
  1. Function pointers



A function pointer is the 'simplest' (in terms of generality; in terms of readability arguably the worst) type a callback can have.

Let's have a simple function
foo:

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


1.1 Writing a function pointer / type notation

A function pointer type has the notation

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


where a named function pointer type will look like

``
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 a function taking int returning int
//

Code Snippets

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
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 the menu
    void update_keybind(unsigned key_id, void(*new_action)())
    {
        actions[key_id] = new_action;
    }
};
game_core_instance.update_keybind(newly_selected_key, &player_jump);

Context

Stack Overflow Q#2298242, score: 715

Revisions (0)

No revisions yet.