C和C++中回调的总结
2016-03-09 22:27
429 查看
原来的网址:http://my.oschina.net/macwe/blog/271609
最近正在改进我的网络库,网络库层和业务层之间的接口如何设计思考了很久,这里总结一下我的思路,欢迎大家探讨。
目录[-]
1 回调函数
2 虚函数
3 函数对象
4 function/bind
5 singal/slot
6 总结
最近正在改进我的网络库,网络库层和业务层之间的接口如何设计思考了很久,这里总结一下我的思路,欢迎大家探讨。
这里先列出本文讨论的集中接口的形式:
函数指针
虚函数
函数对象
function/bind
signal/slot
以上便是c语言中函数回调的通常的方法,就是利用函数指针
接下来看看函数指针的汇编后的样子,以下为上面main函数的部分代码:
通过汇编我们可以看到函数回调本质上就是call,(_当然,这是废话)
接下来我们看看虚函数如何实现回调
这应该是过去C++开发中比较流行的一种方法吧,使用虚函数调用的时候从vtbl中找到实际要实行的函数来运行,本质上就是函数指针,多了一次从vtbl中跳转到实际要执行的函数指针的开销。因为使用还算方便,这点开销还是可以接受的。
在std中使用挺广泛的,比如上面的count_if函数的最后一个参数。他是一个重载了”()“运算符的类,通过和模板的组合使用还是挺灵活的,在实际的项目中会比虚函数那种方式好维护。也没什么开销.
protobuf中的Closure类就是一个例子,值得学习一下。
这种方式算是目前在std支持范围类最优雅的回调的方式了,使用还算简单明了。内部原理还是使用函数对象与模板的结合,没有什么特别的开销,作为服务端开发也是可以接受的(我认为绝大部份的场景都不会对性能有什么影响)。能封装成这么简洁的形式还是能够看到大神们的智慧的。
陈硕大牛的muduo网络库就使用function/bind作为网络库的接口。
这里是一个Qt的例子:
这是目前C++中我认为最方便的一种实现接口的方式了,不管信号的接受函数是否存在都不会有问题,当信号的接收函数不存在时跳过就是了。
他的实现方式有一点点类似与发布-订阅模式的实现,需要一个数据结构(可以是list、map等)来存储singal、slot绑定后的函数对象,当有信号时还需要查找对应的函数对象,会增加额外的开销,这对于高性能的服务端开发是不可原谅的。
因为回调是一个频繁使用的东西,根据使用的场景来判断是否要使用这种机制,比如对性能要求不高的客户端开发使用它还是不错的,当然除非特殊情况一般都不推荐使用C++来开发客户端了_.挺矛盾的,能有C++的场景一般都对性能要求比较高的,所以一般不考虑用这个东西。
当然只要目前计算机处理器的设计思想没有什么重大变化,本质上都是调用汇编中的call指令。我认为以上的几种回调的方法本质上也是函数指针而已,但是随着软件工程和软件规模的发展,人们必然要不断改进使用的方法,不断的将复杂的使用方式做的简单。这样才可以让更多的人能够更好的使用它们。当然如果想提高自己的话还是要弄明白内部到底是怎么回事的。
这里推荐一篇好文章:孟岩的 function/bind的救赎(上)
最近正在改进我的网络库,网络库层和业务层之间的接口如何设计思考了很久,这里总结一下我的思路,欢迎大家探讨。
目录[-]
1 回调函数
2 虚函数
3 函数对象
4 function/bind
5 singal/slot
6 总结
最近正在改进我的网络库,网络库层和业务层之间的接口如何设计思考了很久,这里总结一下我的思路,欢迎大家探讨。
这里先列出本文讨论的集中接口的形式:
函数指针
虚函数
函数对象
function/bind
signal/slot
1 回调函数
#include <stdio.h> // 声明一个回调函数的例子 typedef void (*Handler)(const char *text); void SayHandler(const char *text) { printf("Hello: %s!\r\n", text); } int main(int argc, char **argv) { Handler say; // 函数指针 say = SayHandler; // 给函数指针赋值 say("macwe"); // 调用函数 return 0; }
以上便是c语言中函数回调的通常的方法,就是利用函数指针
接下来看看函数指针的汇编后的样子,以下为上面main函数的部分代码:
main: pushl %ebp movl %esp, %ebp andl $-16, %esp subl $32, %esp movl $SayHandler, 28(%esp) movl $.LC1, (%esp) movl 28(%esp), %eax call *%eax movl $0, %eax leave ret
通过汇编我们可以看到函数回调本质上就是call,(_当然,这是废话)
2 虚函数
接下来我们看看虚函数如何实现回调#include <stdio.h> // 定义一个接口 class IHandler { public: virtual void say(const char *text) = 0; }; // 实现这个接口 class Impl : class public IHandler { public: void say(const char *text) { printf("Hello: %s", text); } }; int main(int argc, char **argv) { IHandler *handler; // 接口指针 Impl impl; // 实现类 handler = dynamic_cast<IHandler *>(&impl); handler.say("macwe"); return 0; }
这应该是过去C++开发中比较流行的一种方法吧,使用虚函数调用的时候从vtbl中找到实际要实行的函数来运行,本质上就是函数指针,多了一次从vtbl中跳转到实际要执行的函数指针的开销。因为使用还算方便,这点开销还是可以接受的。
3 函数对象
#include <iostream> // std::cout #include <algorithm> // std::count_if #include <vector> // std::vector template<typename T> class Pred { private: T _val; public: Pred(T val):_val(val){} bool operator()(T val){ return val>_val; } }; int main (int argc, char **argv) { std::vector<int> myvector; for (int i=1; i<10; i++) myvector.push_back(i); int mycount = count_if (myvector.begin(), myvector.end(), Pred<int>(5)); std::cout << "myvector contains " << mycount; return 0; }
在std中使用挺广泛的,比如上面的count_if函数的最后一个参数。他是一个重载了”()“运算符的类,通过和模板的组合使用还是挺灵活的,在实际的项目中会比虚函数那种方式好维护。也没什么开销.
protobuf中的Closure类就是一个例子,值得学习一下。
4 function/bind
#include <stdio.h> #include <functional> using namespace std::placeholders; class A { public: void say(const char *text) { printf("Hello: %s", text); } }; int main(int argc, char **argv) { std::function<void()> handler; A a; handler = std::bind(&A::say, &a, _1); handler("macwe"); return 0; }
这种方式算是目前在std支持范围类最优雅的回调的方式了,使用还算简单明了。内部原理还是使用函数对象与模板的结合,没有什么特别的开销,作为服务端开发也是可以接受的(我认为绝大部份的场景都不会对性能有什么影响)。能封装成这么简洁的形式还是能够看到大神们的智慧的。
陈硕大牛的muduo网络库就使用function/bind作为网络库的接口。
5 singal/slot
这里是一个Qt的例子:#include <QObject> class Counter : public QObject { Q_OBJECT public: Counter() { m_value = 0; } int value() const { return m_value; } public slots: void setValue(int value); signals: void valueChanged(int newValue); // 声明一个信号 private: int m_value; }; // 这是slot的实现,当m_value值改变的时候就发送valueChanged信号 void Counter::setValue(int value) { if (value != m_value) { m_value = value; emit valueChanged(value); } } Counter a, b; // 将singal和slot连接 QObject::connect(&a, SIGNAL(valueChanged(int)), &b, SLOT(setValue(int))); a.setValue(12); // a.value() == 12, b.value() == 12 b.setValue(48); // a.value() == 12, b.value() == 48
这是目前C++中我认为最方便的一种实现接口的方式了,不管信号的接受函数是否存在都不会有问题,当信号的接收函数不存在时跳过就是了。
他的实现方式有一点点类似与发布-订阅模式的实现,需要一个数据结构(可以是list、map等)来存储singal、slot绑定后的函数对象,当有信号时还需要查找对应的函数对象,会增加额外的开销,这对于高性能的服务端开发是不可原谅的。
因为回调是一个频繁使用的东西,根据使用的场景来判断是否要使用这种机制,比如对性能要求不高的客户端开发使用它还是不错的,当然除非特殊情况一般都不推荐使用C++来开发客户端了_.挺矛盾的,能有C++的场景一般都对性能要求比较高的,所以一般不考虑用这个东西。
6 总结
当然只要目前计算机处理器的设计思想没有什么重大变化,本质上都是调用汇编中的call指令。我认为以上的几种回调的方法本质上也是函数指针而已,但是随着软件工程和软件规模的发展,人们必然要不断改进使用的方法,不断的将复杂的使用方式做的简单。这样才可以让更多的人能够更好的使用它们。当然如果想提高自己的话还是要弄明白内部到底是怎么回事的。这里推荐一篇好文章:孟岩的 function/bind的救赎(上)
相关文章推荐
- C++ 模拟String类 相关
- c++上机报告1-2
- 自考C++程序设计试题2009年1月
- 自考C++程序设计试题2009年10月
- 自考C++程序设计试题2010年10月
- C++接口实现总结
- c++服务器开发 之一 概要介绍
- 自考C++程序设计试题2010年1月
- C语言基础语法
- C++计算绝对值的函数
- [置顶] c++面试常见160问
- c++之输入输出格式
- 【poj 2774】Long Long Message 中文题意&题解&代码(C++)
- contor expansion
- 正态分布,泊松分布,指数分布的c/c++代码
- 2015年第六届蓝桥杯试题(C/C++本科B组)
- 第2周项目2-就拿胖子说事
- c++上机报告1-1
- C++的异常处理
- c语言:利用静态变量static,输出1到5的阶乘值