前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Callbacks in C++11

Callbacks in C++11

作者头像
望天
发布2020-11-24 17:20:04
4200
发布2020-11-24 17:20:04
举报
文章被收录于专栏:along的开发之旅along的开发之旅

写的挺好的入门C++ callback,只是不涉及多线程,没有体现生命周期管理。

Motivation for Callbacks

Imagine that you have a long-running algorithm which takes many iterations to complete. Typically, you will want to provide some kind of feedback to the user to indicate that progress is being made. Otherwise, there is no way of distinguishing between an application that is happliy crunching numbers, and one that is hanging on a dropped network connection. Importantly, not all users will require the same type of feedback. An update could be just about anything you could think of, including:

  • Incrementing an iteration counter at the terminal.
  • Updating a progress bar.
  • Writing intermediate results to disk.
  • Updating a user interface.

The important thing to take away is that different users will have different requirements, and it is difficult or impossible for the author of the algorithm to anticipate all possible actions a user might need.

Callbacks are an ideal paradigm for dealing with this problem. At a high level, the user defines some programatic action which can be added to the algorithm and called at a specified time. This provides quite a bit of flexibility to the user, who can divise any callback they wish, without needing access to the algorithm’s source code. In c++, callbacks are stored as “callables”: i.e., function pointers, pointers to class methods, functors (classes which overload operator()), and lambda functions (since c++11).

Prior to c++11, this was a relatively intimidating topic, since the syntax of function pointers and pointers to class methods involved complicated and unintuitive syntax. However, the advent of the auto keyword, as well as lambda functions, has greatly simplified this topic. In this tutorial, we will cover a very simple callback example in c++11. All examples were compiled with g++ 7.2.0 on Ubuntu 16.04.

A Toy Example

To begin, we define a very simple class, called SquareRoot, containing a single method, double SquareRoot::run(const double), which iteratively approximates the square root of its input using the Babylonian method.

In int main(), we instantiate the class, and call run() with an example input. (We use 1234.5*1234.5 as an input, because we know that the correct output should be 1234.5.)

代码语言:javascript
复制
// Includes
#include <cstdlib>
#include <iostream>
#include <math.h>

// Class with callback
class SquareRoot {
public:
  // Main logic of class.
  double run(const double input) {
    if (input < 0.0) throw 0; // Error checking.
    this->iteration = 0;      // Reset iteration number.
    double guess = input;     // Set initial guess to input.
    // Babylonian method.
    while (std::fabs(guess - input/guess) > this->epsilon) {
      guess = (guess + input / guess) / 2.0;
      ++iteration;
    }
    return guess;
  }

private:
  const double epsilon = 1e-6; // Maximum Allowed Error.
  size_t iteration = 0;        // Iteration Number.
};

int main() {

  SquareRoot p;
  std::cout << "Result: " << p.run(1234.5*1234.5) << std::endl;

  return EXIT_SUCCESS;

}

Let’s save the code as callbacks.cxx, compile, and run:

代码语言:javascript
复制
$ g++ ./callbacks.cxx
$ ./a.out
Result: 1234.5

Success! But what if we want to get more information about how the algorithm is running, such as printing out intermediate guesses? For that, we’ll use callbacks.

Defining a Callback Mechanism

Let’s now define our callback mechanism. For this simple example, our callbacks will take the iteration index and to the intermediate guess (const size_t, const double), and return void. To do this, we will use the std::function template (defined in the <functional> header):

代码语言:javascript
复制
using TCallback = std::function<void(const size_t, const double)>;

A TCallback instance can be used to store all the callables described above: function pointers, pointers to class methods, functors, and lambda functions. Since a user may want to add more than one callback, it is generally a good idea to store a std::vector of callbacks, rather than a single one. The type of the callback vector is defined as follows:

代码语言:javascript
复制
using TCallbackVector = std::vector<TCallback>;

We can then define a private data member to hold the callback vector…

代码语言:javascript
复制
private:
  // Data member holding callbacks.
  TCallbackVector m_callbacks;

…and a class method to add callbacks to the vector.

代码语言:javascript
复制
// Push callbacks onto the stack.
void add_callback(TCallback cb) {
  m_callbacks.push_back(cb);
}

Finally, we add logic to the Program::run method, which invokes each callback prior to each update:

代码语言:javascript
复制
// Main logic of class, where callbacks are invoked.
void run() {
  // ...
  for (const auto &cb : m_callbacks) {
    cb(iteration, guess);
  }
  // ...
}

We can now put it all together into a complete SquareRoot class:

代码语言:javascript
复制
// Includes
#include <cstdlib>
#include <iostream>
#include <math.h>
#include <vector>
#include <functional>

// Class with callback
class SquareRoot {
public:
  // Callback typedefs
  using TCallback = std::function<void(const size_t, const double)>;
  using TCallbackVector = std::vector<TCallback>;

  // Push callbacks onto the stack.
  void add_callback(TCallback cb) {
    m_callbacks.push_back(cb);
  }

  // Main logic of class.
  double run(const double input) {
    if (input < 0.0) throw 0; // Error checking.
    this->iteration = 0;      // Reset iteration number.
    double guess = input;     // Set initial guess to input.
    // Babylonian method.
    while (std::fabs(guess - input/guess) > this->epsilon) {
      for (const auto &cb : m_callbacks) {
        cb(iteration, guess);
      }
      guess = (guess + input / guess) / 2.0;
      ++iteration;
    }
    return guess;
  }

private:
  const double epsilon = 1e-6; // Maximum Allowed Error.
  size_t iteration = 0;        // Iteration Number.

  // Data member holding callbacks.
  TCallbackVector m_callbacks;

};

int main() {

  SquareRoot p;
  std::cout << "Result: " << p.run(1234.5*1234.5) << std::endl;

  return EXIT_SUCCESS;

}

At this point, we have added the structure necessary to allow the user to add callbacks; but we haven’t actually added any. So you’re welcome to compile and run this code again, but the output should be the same.

Defining the Callbacks

For this example, we will define four callbacks: one for each type of callable discussed above.

Function Pointer

To define a callback as a function pointer, we begin by defining a function matching the signature we used in TCallback:

代码语言:javascript
复制
void FunctionPointerCallback(const size_t iteration, const double guess) {
  std::cout << iteration << " : " << guess << " (Function Pointer)\n";
}

Moreover, since c++11, we can use auto to easily define a function pointer, and add it to our Program instance, p:

代码语言:javascript
复制
auto *cb_a = FunctionPointerCallback;
p.add_callback(cb_a);

By using auto, we have avoided the cumbersome function pointer syntax we would have needed to contend with prior to c++11.

Pointer to Member Function

While a function is simple and convenient, it is sometimes useful to have the flexibility of a full class instance (if, for example, you need to store data or organize functionality into multiple methods). Defining such a callback is as simple as defining a class (again, keeping in mind that the function signature of the callback method must match that of TCallback):

代码语言:javascript
复制
class MemberFunctionCallback {
public:
  void Call(const size_t iteration, const double guess) {
    std::cout << iteration << " : " << guess << " (Member Function)\n";
  }
};

Defining a pointer to a member function is simple using auto:

代码语言:javascript
复制
auto cb = &MemberFunctionCallback::Call;

However, this cannot be passed directly to add_callback. The reason for this can be understood by recognizing that, though the function signature of cb may appear to match TCallback, in actuality cb takes an invisible first argument (*this) to a particular instance of the class. Luckily, this can be dealt with painlessly by using std::bind, which allows you to “bind” arguments to a callable, effectively creating a new callable where those arguments are implied. In this case, we bind a pointer to the class instance to the member function pointer, and use std::placeholders::_1 to indicate that the remaining argument (the iteration index) will be supplied during the invocation. The result is a new callable whose function signature matches TCallback:

代码语言:javascript
复制
MemberFunctionCallback cb_b_tmp;
auto cb_b = std::bind(&MemberFunctionCallback::Call, // function
                      &cb_b_tmp,                     // First argument (*this)
                      std::placeholders::_1,         // 1st placeholder
                      std::placeholders::_2);        // 2nd placeholder
p.add_callback(cb_b);

Functor

Although using std::bind is an elegant and quite readable solution to the problem of using a member function pointer as a callback, even this small bit of added complexity can be avoided by converting the callback in the previous section to a functor: i.e., a class which overloads operator().

代码语言:javascript
复制
class FunctorCallback {
public:
  void operator()(const size_t iteration, const double guess) {
    std::cout << iteration << " : " << guess << " (Functor)\n";
  }
};

Since an instance of a functor is itself a callable (unlike a method pointer), it can be passed directly to add_callback:

代码语言:javascript
复制
FunctorCallback cb_c;
p.add_callback(cb_c);

Lambda Function

The last type of callable we’ll cover is the lambda function (introduced in c++11). Using auto, a lambda function instance can be easily captured and passed to add_callback:

代码语言:javascript
复制
auto cb_d = [](const size_t iteration, const double guess)
  { std::cout << iteration << " : " << guess << " (Lambda)\n"; };
p.add_callback(cb_d);

Putting it All Together

In this post, we’ve discussed a motivating problem which callbacks address; constructed a toy example for experimenting with their implementation; defined a simple callback mechanism; and shown four types of callables compatible with this mechanism. Here is the completed example, demonstrating how all the pieces we’ve discussed fit together.

代码语言:javascript
复制
// Includes
#include <cstdlib>
#include <iostream>
#include <math.h>
#include <vector>
#include <functional>

void FunctionPointerCallback(const size_t iteration, const double guess) {
  std::cout << iteration << " : " << guess << " (Function Pointer)\n";
}

class MemberFunctionCallback {
public:
  void Call(const size_t iteration, const double guess) {
    std::cout << iteration << " : " << guess << " (Member Function)\n";
  }
};

class FunctorCallback {
public:
  void operator()(const size_t iteration, const double guess) {
    std::cout << iteration << " : " << guess << " (Functor)\n";
  }
};

// Class with callback
class SquareRoot {
public:
  // Callback typedefs
  using TCallback = std::function<void(const size_t, const double)>;
  using TCallbackVector = std::vector<TCallback>;

  // Push callbacks onto the stack.
  void add_callback(TCallback cb) {
    m_callbacks.push_back(cb);
  }

  // Main logic of class.
  double run(const double input) {
    if (input < 0.0) throw 0; // Error checking.
    this->iteration = 0;      // Reset iteration number.
    double guess = input;     // Set initial guess to input.
    // Babylonian method.
    while (std::fabs(guess - input/guess) > this->epsilon) {
      for (const auto &cb : m_callbacks) {
        cb(iteration, guess);
      }
      guess = (guess + input / guess) / 2.0;
      ++iteration;
    }
    return guess;
  }

private:
  const double epsilon = 1e-6; // Maximum Allowed Error.
  size_t iteration = 0;        // Iteration Number.

  // Data member holding callbacks.
  TCallbackVector m_callbacks;

};

int main() {

  SquareRoot p;

  // Function Pointer
  auto *cb_a = FunctionPointerCallback;
  p.add_callback(cb_a);

  // Member Function
  MemberFunctionCallback cb_b_tmp;
  auto cb_b = std::bind(&MemberFunctionCallback::Call, // function
                        &cb_b_tmp,                     // First argument (*this)
                        std::placeholders::_1,         // 1st placeholder
                        std::placeholders::_2);        // 2nd placeholder
  p.add_callback(cb_b);

  // Functor
  FunctorCallback cb_c;
  p.add_callback(cb_c);

  // Lambda
  auto cb_d = [](const size_t iteration, const double guess)
    { std::cout << iteration << " : " << guess << " (Lambda)\n"; };
  p.add_callback(cb_d);

  std::cout << "Result: " << p.run(1234.5*1234.5) << std::endl;

  return EXIT_SUCCESS;

}

Callbacks are a striking example of how the improvements in c++11 can greatly simplify the syntax for important programming paradigms. I hope that this post provide a simple starting point for implementing callbacks in your own project, and perhaps that it will spark interest in exploring c++11 (and c++14, and c++17, and beyond!) in greater depth, to see how our lives as writers and readers of code can be made easier with modern c++. Happy coding! :-)

转自:

https://sudomakeinstall.io/posts/2017/11/30/callbacks-in-cpp11/

本文系外文翻译,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文系外文翻译前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Motivation for Callbacks
  • A Toy Example
  • Defining a Callback Mechanism
  • Defining the Callbacks
    • Function Pointer
      • Pointer to Member Function
        • Functor
          • Lambda Function
          • Putting it All Together
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档