Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Callbacks in C++11

Callbacks in C++11

作者头像
望天
发布于 2020-11-24 09:20:04
发布于 2020-11-24 09:20:04
48000
代码可运行
举报
文章被收录于专栏:along的开发之旅along的开发之旅
运行总次数:0
代码可运行

写的挺好的入门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
代码运行次数:0
运行
AI代码解释
复制
// 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
代码运行次数:0
运行
AI代码解释
复制
$ 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
代码运行次数:0
运行
AI代码解释
复制
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
代码运行次数:0
运行
AI代码解释
复制
using TCallbackVector = std::vector<TCallback>;

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
private:
  // Data member holding callbacks.
  TCallbackVector m_callbacks;

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 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
代码运行次数:0
运行
AI代码解释
复制
// 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
代码运行次数:0
运行
AI代码解释
复制
// 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
代码运行次数:0
运行
AI代码解释
复制
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
代码运行次数:0
运行
AI代码解释
复制
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
代码运行次数:0
运行
AI代码解释
复制
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
代码运行次数:0
运行
AI代码解释
复制
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
代码运行次数:0
运行
AI代码解释
复制
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
代码运行次数:0
运行
AI代码解释
复制
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
代码运行次数:0
运行
AI代码解释
复制
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
代码运行次数:0
运行
AI代码解释
复制
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
代码运行次数:0
运行
AI代码解释
复制
// 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 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
C++11的简单介绍(下)
上述代码就是使用C++11中的lambda表达式来解决,可以看出lambda表达式实际是一个匿名函数。
ahao
2024/03/19
1330
C++11的简单介绍(下)
C++11常用的一部分新特性
C++11扩大了用大括号括起的列表(初始化列表)的使用范围,使其可用于所有的内置类型和用户自 定义的类型,使用初始化列表时,可添加等号(=),也可不添加。
有礼貌的灰绅士
2023/06/14
4780
C++11常用的一部分新特性
C++11(3)
C++11 的新特性可变参数模板能够让您创建可以接受可变参数的函数模板和类模板,相比C++98/03,类模版和函数模版中只能含 固定数量 的模版参数,可变模版参数无疑是一个巨大的改
啊QQQQQ
2024/11/19
1030
C++11(3)
【c++】一篇文章带你了解c++11的新特性&&c++11详解
在2003年C++标准委员会曾经提交了一份技术勘误表(简称TC1),使得C++03这个名字已经取代了C++98称为C++11之前的最新C++标准名称。不过由于C++03(TC1)主要是对C++98标准中的漏洞进行修复,语言的核心部分则没有改动,因此人们习惯性的把两个标准合并称为C++98/03标准。
用户10925563
2024/08/06
2410
【c++】一篇文章带你了解c++11的新特性&&c++11详解
【c++11】包装器
包装器(Wrapper) 是一个常见的编程设计模式,通常用于封装或“包装”某个现有的对象、函数、数据结构或者操作,以提供额外的功能或简化接口。在不同的上下文中,包装器可能有不同的实现方式和目的,但核心思想都是“将现有功能封装起来,以实现更强的扩展性、易用性或者功能分离”。
用户11029103
2025/02/03
1470
【c++11】包装器
【C++航海王:追寻罗杰的编程之路】C++11(四)
在C++98中,如果想要对一个数据集合中的元素进行排序,可以使用std::sort方法。
枫叶丹
2024/06/04
1600
【C++航海王:追寻罗杰的编程之路】C++11(四)
【C++高阶】:C++11的深度解析下
C++11的新特性可变参数模板能够让您创建可以接受可变参数的函数模板和类模板,相比C++98,类模版和函数模版中只能含固定数量的模版参数,可变模版参数无疑是一个巨大的改进。
IsLand1314
2024/10/15
1410
【C++高阶】:C++11的深度解析下
[C++11札记]: std::function
在C/C++中函数指针作为一种回调机制被广泛使用,但是函数指针在C++面向对象编程中有些不足,比如无法捕捉上下文。举个例子,使用对象的非静态成员函数作为函数指针就无法做到。
云水木石
2019/07/01
1.4K0
【C++】C++11
C++11 是 C++ 的第⼆个主要版本,并且是从 C++98 起的最重要更新。它引⼊了⼤量更改,标准化了既 有实践,并改进了对 C++ 程序员可⽤的抽象。在它最终由 ISO 在 2011 年 8 ⽉ 12 ⽇采纳前,⼈们曾使⽤名称“C++0x”,因为它曾被期待在 2010 年之前发布。C++03 与 C++11 期间花了 8 年时间,故⽽这是迄今为⽌最⻓的版本间隔。从那时起,C++ 有规律地每 3 年更新⼀次。
用户11290673
2025/02/05
1270
【C++】C++11
C++11特性大杂烩
在2003年C++标准委员会曾经提交了一份技术勘误表(简称TC1),使得C++03这个名字已经取代了C++98称为C++11之前的最新C++标准名称。不过由于C++03(TC1)主要是对C++98标准中的漏洞进行修复,语言的核心部分则没有改动,因此人们习惯性的把两个标准合并称为C++98/03标准。
梨_萍
2023/05/11
9520
C++11特性大杂烩
跟Google学写代码--Chromium工程中用到的C++11特性
Ttile 跟Google学写代码--Chromium工程中用到的C++11特性 Chromium是一个伟大的、庞大的开源工程,很多值得我们学习的地方。 《跟Google学写代码–Chromium/base–stl_util源码学习及应用》 《跟Google学写代码–Chromium/base–windows_version源码学习及应用》 《跟Google学写代码–Chromium/base–cpu源码学习及应用》 今天就与大家一起分享一下Chromium中所用到的C++11特性,有的是之前博客没介绍过
程序员的酒和故事
2018/03/12
1.4K0
C++11
相比于C++98/03,C++11则带来了数量可观的变化,其中包含了约140个新特性,以及对C++03标准中 约600个缺陷的修正,这使得C++11更像是从C++98/03中孕育出的一种新语言。相比较而言,C++11能更好地用于系统开发和库开发、语法更加泛华和简单化、更加稳定和安全,不仅功能更强大,而且能提升程序员的开发效率,公司实际项目开发中也用得比较多,所以我们要作为一个 重点去学习。
小灵蛇
2024/06/06
1760
C++11
【C++11】解锁C++11新纪元:深入探索Lambda表达式的奥秘
前言:在C++编程语言的悠久历史中,每一次标准的更新都带来了革命性的变化,推动了编程范式和性能优化的新边界。C++11标准,作为这一漫长演进过程中的一个重要里程碑,不仅巩固了C++作为高性能系统级编程语言的地位,还引入了众多现代编程特性,极大地丰富了C++的表达力和易用性。其中,lambda表达式和std::function无疑是这些新特性中最引人注目且影响深远的两个
Eternity._
2024/08/05
1350
【C++11】解锁C++11新纪元:深入探索Lambda表达式的奥秘
C++11中lambda表达式与包装器
在C++98中,如果想要对一个数据集合中的元素进行排序,可以使用std::sort方法。
用户11317877
2024/10/31
1020
C++11中lambda表达式与包装器
C/C++开发基础——函数对象与std::function模板
函数对象可以被当作一个值赋给另一个变量,也可以作为实参传递给其他函数,或者作为其他函数的返回结果。
Coder-ZZ
2023/02/23
9270
C/C++开发基础——函数对象与std::function模板
C++可调用Callable类型的总结
自从在使用 std::thread 构造函数过程中遇到了 Callable 类型的概念以来用到了很多关于它的使用. 因此本文把使用/调查结果总结出来. 包括 Callable 的基础概念, 典型的 Callable 类型介绍. 例如函数对象(狭义), 函数指针, lambda 匿名函数, 函数适配器, std::function 仿函数等.
高性能架构探索
2023/09/05
3380
C++可调用Callable类型的总结
C++11-lambda表达式/包装器/线程库
捕捉列表,该列表总是出现在lambda函数的开始位置,编译器根据[]来判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量供lambda函数使用
用户9645905
2022/11/30
1.2K0
C++11-lambda表达式/包装器/线程库
【C++11】C++11——包装器
function包装器也叫做适配器,C++11中的function本质是一个类模板,也是一个包装器。
平凡的人1
2023/10/15
3070
【C++11】C++11——包装器
【C++】————C++11
1998年是C++标准委员会成立的第一年,本来计划以后每5年视实际需要更新一次标准,C++国际标准委员会在研究C++ 03的下一个版本的时候,一开始计划是2007年发布,所以最初这个标准叫C++07。但是到06年的时候,官方觉得2007年肯定完不成C++ 07,而且官方觉得2008年可能也完不成。最后干脆叫C++0x。x的意思是不知道到底能在07还是08还是09年完成。结果2010年的时候也没完成,最后在2011年终于完成了C++标准。所以最终定名为C++11。
用户11036582
2024/08/16
960
【C++】————C++11
C++11新特性概览
今天买的《C++ Primer 第五版》到了,这一版本一个比较好的地方是。在开始的目录里面列出来了全书中涉及到的C++11新特性的地方,标明了页码,可以直接到对应的页面去看新特性的东西。于是我对照书上的例子,写了一些简单的示例,用来大概的了解C++11的新特性,总结在这里,以后可以查查。
王云峰
2019/12/25
4520
相关推荐
C++11的简单介绍(下)
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验