java 的实现方式

java的回调函数可能都不陌生,使用接口interface的方式,在接口中定义回调函数。函数参数可以是interfance。调用函数的时候,实现这个interface的函数即可。

简单示例:

public interface CallBack {
    public void execute();
}

public void test(CallBack callBack){
        System.out.println("callback");
        callBack.execute(); 
}

test(new CallBack() {
    @Override
    public void execute() {
        System.out.println("callback implement");
    }
});

c++的实现

c++是通过函数指针(c的语法)和std::function(c++11里面的语法)来实现的。

c++ 回调函数的使用场景有下面几种:

回调函数是普通函数

回调函数的使用场景一般是,一个函数中最后产生一个结果,该函数不再去管这个结果后续的使用,而使用回调函数进行处理。

可以先定义好回调函数的函数指针,一般格式:

返回值 (*指针名) (参数列表)

typedef void (*CaptureCallback)(string);


void capturePic(CaptureCallback callback){
    string t = "a pic";
    callback(t);
}

void renderPic(string t){
    print(t);
}

capturePic(renderPic);

上面就是一个简单的例子,在捕获图片的函数里面使用渲染图片的回调函数。

回调函数是成员函数

在c++面向对象里面,回调函数是成员函数的情况更常见,这样的好处是,一个类A的一个函数生成一个结果之后,可以调用另一个类B的成员函数。而不必类A拥有B的实例。

尤其是在B中有了A的实例的情况下,A中如果再包含B的实例会出现循环引用的问题,这也是可以解决的,但是这种耦合还是容易让逻辑变得混乱。

typedef std::function<void (string)> CaptureCallback;  

class CaptureController{
public:
    CaptureCallback callback;
    CaptureController(CaptureCallback callback):callback(callback){};
    void capturePic(CaptureCallback callback){
        string t = "a pic";
        callback(t);
    }
}


class UI{
    //开始捕捉图像
    void startCapture(){
        CaptureController c(std::bind(&UI::renderPic,this,_1)); 
        c.capturePic();
    }
  
    //渲染图片作为回调函数
    void renderPic(string t){
        print(t);
    }
}

//main.cpp
UI ui;
ui.startCapture();

上面的例子就很好的说明了为什么需要回调函数,以及回调函数是如何使用的。

在UI的类中已经引用了CaptureController的头文件了,如果不使用回调函数,就必须在CaptureController.h中也引用UI.h 的头文件,这样才能访问到UI里面的 renderPice。这样就会造成循环引用头文件的问题。

std::function

类的成员函数作为回调函数与普通函数不同,使用的不是函数指针,而是 std::function<返回类型(参数类型...)> 函数名称

typedef std::function<void (string)> CaptureCallback;  
std::bind

我们在传入一个函数作为参数的时候,需要使用 std::bind(oldFunName,arg_list)bind()函数会返回一个新的函数对象,arg_list指的是旧的函数对象 oldFunName的参数列表。

arg_list中的 _1_2才是新的函数对象的参数列表。_1这种被称为占位符。在std::placeholders的命名空间下。

CaptureCallback callback = std::bind(&UI::renderPic,this,_1);
callback("test");

当调用 callback("test"),实际上调用的是 UI对象的成员函数 this.renderPir("test")所以这里面还需要多一个 this的对象指针

特别的,std::bind函数返回的新的函数对象的参数数目可以与oldFunName的参数数目不同。

举例:

void renderPic(string t,int a,char b);
CaptureCallback callback = std::bind(&UI::renderPic,this,_1,2,'b');
callback("test");

这里调用 callback("test"),实际上是调用了 renderPic("test",2,'b')这个函数。

当然这种回调函数的参数数目与传入的函数参数数目不同的应用场景很少见,可以忽略。


需要注意的是,std::functionstd::bind都是c++11标准才有的语法。
也是从boost这个c++扩展库中拿过来的。

回调函数既可以是普通函数也可以是成员函数

其实就是第二种方式,使用 std::function可以取代c语言中的函数指针,同时需要注意的是,如果是普通函数传入,则不需要传入 this的对象指针。

回调函数并不是一定要用

以上面的摄像头捕获图片->渲染图片的例子为例。

有两种方法也可以实现,先捕获图片在渲染界面的顺序:

方法1

    void startCapture(){
        string t;
        CaptureController c(); 
        c.capturePic(t);//参数是string的引用
        renderPic(t);
    }
    void renderPic(string t){
        print(t);
    }

通过在 startCapture内部先调用 capturePic的函数,再调renderPic函数处理这个结果也可以。但是大多应用场景是不用线程的

如果是不同线程还可以用方法二。

方法2

renderPic 开一个定时器,定时去取capturePic函数产生的结果,也可以,但是此时renderPic的节奏就和capturePic的节奏不一致。

所以,是否需要使用回调函数,需要根据当前的应用场景去选择。


参考文章:

最后修改:2020 年 07 月 24 日
喜欢我的文章吗?
别忘了点赞或赞赏,让我知道创作的路上有你陪伴。