文档章节

C和C++中回调的总结

macwe
 macwe
发布于 2014/06/01 21:39
字数 1400
阅读 738
收藏 3
点赞 0
评论 1

最近正在改进我的网络库,网络库层和业务层之间的接口如何设计思考了很久,这里总结一下我的思路,欢迎大家探讨。

这里先列出本文讨论的集中接口的形式:

  • 函数指针
  • 虚函数
  • 函数对象
  • 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的救赎(上)

© 著作权归作者所有

共有 人打赏支持
macwe
粉丝 13
博文 19
码字总数 22304
作品 0
海淀
其他
加载中

评论(1)

渡世白玉
渡世白玉
学习了、、
现在C++11了、还是bind和信号槽好些、、
只是你说信号槽的效率不可接受也不是绝对的、、毕竟其他语言怎么优化达到这个效率都很难的、、、Qt5新语法用指针代替字符串了、、还好点、、就是类型的检查和用list一类的容器是少不了了、
使用 acl 库编写高并发非阻塞网络通信程序

一、概述 acl 库的 C 库(libacl) 的 aio 模块设计了完整的非阻塞异步 IO 通信过程,在 acl 的C++库(libaclcpp) 中封装并增强了异步通信的功能,本文主要描述了 acl C++ 库之非阻塞IO库的设计...

郑树新
2014/08/25
0
0
jni在C/C++代码中调用java函数,java函数的参数是接口,有办法吗?

先上一段安卓下面搜索BLE的代码: 这里面的this是一个回调接口,当搜索到蓝牙设备时,会回调该接口。 在安卓上面用java没问题,但是现在需要在C++函数中实现这一段。 步骤描述: C++: 1 获取...

lvrenyang
05/22
0
0
nodejs的C++扩展中实现异步回调

在nodejs的官方网站中有关于C++扩展的详细说明,其中包含了从"hello world"到对象封装的一系列示例。其中的“callback”节是关于回调函数的,美中不足的是,这个回调是阻塞的回调。 官方示例...

GZShi_alpha
2014/07/01
0
2
为什么要使用extern "C"

C/C++采用的是分别编译模型, 源代码只要声明函数, 就可调用。 编译时,在函数调用处生成一个符号引用。 链接时,将函数调用处的符号引用,替换成地址(甚至仍有可能继续保留符号, 载入时再...

borey
2014/10/14
0
0
php数组array_filter()函数和array_slice()函数

<?php / arrayfilter()用回调函数过滤数组中的单元 arrayfilter(array,function) 参数描述:如果自定义过滤函数返回 true,则被操作的数组的当前值就会被包含在返回的结果数组中, 并将结果组成...

BearCatYN
2015/03/26
0
0
javascript引擎在c,c+中调用

JavaScript是一种广泛用于Web客户端开发的脚本语言,常用来控制浏览器的DOM树,给HTML网页添加动态功能。目前JavaScript遵循的web标准的是ECMAScript262。由于JavaScript提供了丰富的内置函数...

crossmix
2015/04/19
0
0
C语言/C++程序员编程学习自信心曲线图

C语言是面向过程的,而C++是面向对象的 C和C++的区别: C是一个结构化语言,它的重点在于算法和数据结构。C程序的设计首要考虑的是如何通过一个过程,对输入(或环境条件)进行运算处理得到...

小辰带你看世界
05/10
0
0
C#调用C的Dll(类型对照)

C#调用C的DLL //C++中的DLL函数原型为 //extern "C" declspec(dllexport) bool 方法名一(const char 变量名1, unsigned char 变量名2) //extern "C" declspec(dllexport) bool 方法名二(cons......

KavenSu
2014/04/30
0
0
用多线程方法实现在MFC/WIN32中调用OpenGL函数并创建OpenGL窗口

OpenGL相关的工具库中的OpenGL程序往往都是在C函数main中初始化和创建的,使用控制台来完成显示和控制颇为不便。如果能够在MFC中OpenGL函数并创建OpenGL窗口,并且可以将控制参数传入给OpenG...

guoliang
2014/05/29
0
0
c++回调函数 callback

C++中实现回调机制的几种方式 (1)Callback方式 Callback的本质是设置一个函数指针进去,然后在需要需要触发某个事件时调用该方法, 比如Windows的窗口消息处理函数就是这种类型。比如下面的...

涩女郎
2015/08/12
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

Linux服务器下的HTTP抓包分析

说到抓包分析,最简单的办法莫过于在客户端直接安装一个Wireshark或者Fiddler了,但是有时候由于客户端开发人员(可能是第三方)知识欠缺或者其它一些原因,无法顺利的在客户端进行抓包分析,...

mylxsw
5分钟前
0
0
mybatis3-javaapi

sqlSessionFactoryBuilder->sqlSessionFactory->sqlSession<-rowbound<-resultHandler myBatis uses a Java enumeration wrapper for transaction isolation levels, called TransactionIsol......

writeademo
8分钟前
0
0
Java NIO:浅析I/O模型

也许很多朋友在学习NIO的时候都会感觉有点吃力,对里面的很多概念都感觉不是那么明朗。在进入Java NIO编程之前,我们今天先来讨论一些比较基础的知识:I/O模型。下面本文先从同步和异步的概念...

yzbty23
9分钟前
0
0
了解iOS消息推送一文就够:史上最全iOS Push技术详解

本文作者:陈裕发, 腾讯系统测试工程师,由腾讯WeTest整理发表。 1、引言 开发iOS系统中的Push推送,通常有以下3种情况: 1)在线Push:比如QQ、微信等IM界面处于前台时,聊天消息和指令都会...

JackJiang-
10分钟前
0
0
Mysql汉子转拼音

update t_app_city SET CITY_NAME_BEGIN = ELT(INTERVAL(CONV(HEX(LEFT(CONVERT(CITY_NAME USING gbk),1)),16,10), 0xB0A1,0xB0C5,0xB2C1,0xB4EE,0xB6EA,0xB7A2,0xB8C1,0xB9FE,0xBBF7, 0xBFA......

尘叙缘
12分钟前
0
0
大数据构建智慧城市“新引擎”,加速推进新旧动能转换

——“大数据与智慧城市”技术交流分享会——济南站召开 7月13日,“大数据携手智慧城市,助力山东新旧动能转换”技术交流分享会——济南站在山东信息通信技术研究院会议室成功举办,此次会议...

左手的倒影
14分钟前
2
0
tomcat 学习笔记之 Session管理

1、Catalina 通过一个 Session 管理器的组件来管理建立的Session 对象 该组件由 org.apache.catalina.Manager 接口表示 Session 管理器必须与一个 Context 关联 Session 管理器负责,创建、更...

职业搬砖20年
15分钟前
0
0
jquery获取input框的几种方式

//如何用jquery获取<input id="test" name="test" type="text"/>中输入的值?$(" #test ").val()$(" input[ name='test' ] ").val()$(" input[ type='text' ] ").val()$(" input[ ......

gulf
18分钟前
0
0
gradle的环境变量的配置

gradle的环境变量的配置 1.首先下载jdk,并且配置jdk的环境变量. 2.找到自己AS安装gradle的目录 我自己的目录为:F:\Android Studio3.1.3\gradle\gradle-4.4 创建环境变量:GRADLE_PATH: F:\A...

android-key
24分钟前
0
0
saltstack配置apache

1.相关配置 #vim /etc/salt/master //打开如下内容的注释 file_roots: base: - /srv/salt #mkdir /srv/salt #vim /srv/salt/top.sls base: 'slaver.test.com': - apache 注意:若换成 '*',则......

硅谷课堂
24分钟前
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部