如何避免复制回调函数(C++)

4
我正在使用一个只接受void回调函数的API:
void (* CALLBACKFUNC) (void);
我想调用带有参数的回调函数,而不是为不同的输入参数编写多个具有相同功能的回调函数。比方说,我需要一个类似于以下的回调函数:
void myFunc(int a);
更新:更多信息:根据事件调用回调函数应该如下所示:
event1 -> 调用myFunc(1); event2 -> 调用myFunc(2); ...
事件数量有限且已预定义了最大值(如果有帮助),但我不想复制功能(实际情况中存在多个输入值,为不同的组合复制函数调用不容易)。
附注:我也可以使用C++11。有什么建议吗?

1
你尝试过什么?请展示一些(相关的)代码。如果你有错误,请也一并展示。 - Some programmer dude
1
我尝试传递一个函数对象而不是函数指针,显然我得到了错误:无法将参数x从'FUNCTOR_TYPE'转换为'CALLBACKFUNC'。 - towi_parallelism
你可以使用 std::functionstd::bind 来创建定制的回调函数。这些是 C++11 的特性。 - Rishit Sanmukhani
3
你是在设计API还是正在找出与现有API集成的最佳方式? - doron
不,我没有设计API。 我只是在使用它。 但是我的应用程序需要非void回调函数。 - towi_parallelism
显示剩余2条评论
5个回答

4
这个解决方案怎么样?您不需要手动定义新函数来设置不同的状态。
#include <iostream>

void setState(int s) {

    std::cout << "Set state to " << s << std::endl;

}

template <int n>
void myWrapper() {
    setState(n);
}


void myLogic(void(*CALLBACK)(void)) {

    CALLBACK();
}


int main(int argc, char* argv[]) {


    myLogic(myWrapper<50>);
    myLogic(myWrapper<100>);

}

好的,我接受了这个解决方案,但我不知道“非类型模板参数”应该在编译时知道。我不明白这有什么用。我的意思是,如果不能向包装器传递参数,那么它就没有帮助,对吧? - towi_parallelism
1
@TOWI_Parallelism:没错,那么请取消接受答案,这样其他人就能看到这个问题还没有得到解答。我会考虑一下的。 - Krab

2
如果您必须传递一个 void (* CALLBACKFUNC) (void),并且不能更改它,那么您无法传递状态函数。但是,您可以共享功能:
void myFunc (int a) {
   std::cout << a;
}
void myFunc1() {myFunc(1);}
void myFunc2() {myFunc(2);}
void myFunc3() {myFunc(3);}
void myFunc4() {myFunc(4);}
void myFunc5() {myFunc(5);}

int main() {
    use_callback(myFunc1);
    use_callback(myFunc2);
    use_callback(myFunc3);
    use_callback(myFunc4);
    use_callback(myFunc5);
}

如果这个方法对你不起作用,那么你需要重新设计你的代码,或者使用全局变量来传递状态。如果你一定要这样做,但我不建议这样做,你可以使用回调函数的数组,并为每个回调函数提供自己的全局状态。

 struct callback_state {
     int p;
 };
 struct callback_meta {
     bool is_used = false;
     CALLBACKFUNC func;
     callback_state state {};
 };
 static const size_t max_callbacks = 6;
 callback_meta meta_array[max_callbacks];
 lock_type global_callback_lock; //of course you'll need a lock
 #define GLUE2(X,Y) X##Y
 #define GLUE(X,Y) GLUE2(X,Y)
 #define DEFINE_CALLBACK(X) void GLUE(func,X) { \
         real_func(meta_array[X].state.p); \
         globcal_callback_lock.lock(); \
         meta_array[i].is_used = false; \
         globcal_callback_lock.unlock(); \
     } \
     meta_array[X].func = GLUE(func,X);

 CALLBACKFUNC prepare_callback(callback_state state) {
     CALLBACKFUNC ret = 0;
     globcal_callback_lock.lock();
     for(int i=0; i<max_callbacks; ++i) {
         if(meta_array[i].is_used == false) {
             meta_array[i].state = state;
             meta_array[i].is_used = true;
             ret = meta_array[i].func;
             break;
         }
     }
     globcal_callback_lock.unlock();         
     return ret;
 }
 DEFINE_CALLBACK(0); //boost preprocessor would go a long way here
 DEFINE_CALLBACK(1);
 DEFINE_CALLBACK(2);
 DEFINE_CALLBACK(3);
 DEFINE_CALLBACK(4);
 DEFINE_CALLBACK(5);

非常感谢 @Mooing Duck!你的思考和编码速度真让我惊叹!虽然只能接受一个答案!不过我想我会选择 Krab 的回答。 - towi_parallelism

1

有一个解决方案,但更像是一种黑客技巧,不具备平台独立性。它仅适用于32位x86架构并使用cdecl调用约定。

#include <iostream>

const int PARAMS = 0xCAFEBABE; // set this value to be something unique (read below)

typedef void(*CALLBACK)(void);

void functionICantModify(CALLBACK cb) {

    cb();

}

/*
 * It depends on how on x86 with cdecl calling convention the stack frame
 * is generated by most compilers.
 *
 *
 * Stack frame when called as myCallback();
 *        <--- esp is now same as ebp and points to old ebp on stack (mov ebp, esp)
 * [ebp] ; this is old ebp (push ebp)
 * [returnAddress] ; return address to functionICantModify
 * [undefined] ; possibly local variable of functionICantModify or old ebp
 *
 *
 * Stack frame when called as myCallback(PARAMS, 10, 50)
 *        <--- esp is now same as ebp and points to old ebp on stack (mov ebp, esp)
 * [ebp] ; this is old ebp (push ebp)
 * [returnAddress] ; return address to function from where i called myCallback(PARAMS, ...)
 * [PARAMS] ; this is our flag
 * [param1]
 * [param2]
 *
 *
 * ebp register acts like a frame pointer.
 * When access to local variables, ebp is substracted (first local 32bit variable is [ebp-4], second [ebp-8], ...)
 * When access to function arguments, ebp is added (first 32bit argument is [ebp+8], because [ebp] points to old ebp and [ebp+4] is return address (on 32bit architecture)
 *
 * You can see from those stack frames abowe that when you will access the paramsFlag in myCallback when
 * myCallback called from functionICantModify, you will get that undefined value. So it is important
 * to set the PARAMS value to be something unique that will never appears on stack in that place when
 * myCallback is called. By checking paramsFlag, you can detect if the function is called by
 * functionICantModify or by your logic.
 */
void myCallback(int paramsFlag, int param1, int param2) {

    /*
     * This is how prolog is generated by most compilers on x86 (intel syntax)
     * push ebp
     * mov ebp, esp
     */
    static int _param1 = 0;
    static int _param2 = 0;

    if (paramsFlag == PARAMS) { // called from my logic, setup state
        std::cout << "Called to set state";
        _param1 = param1;
        _param2 = param2;
    }
    else { // called from functionICantModify, don't access the function parameters !!!!!!!!!!!!!
        std::cout << "Called from functionICantModify, _param1=" << _param1 << ", _param2=" << _param2 << std::endl;
    }

    /*
     * This is how epilog is generated by most compilers on x86 with cdecl (intel syntax)
     * mov esp, ebp
     * pop ebp
     * ret
     */
}

int main(int argc, char* argv[]) {

    myCallback(PARAMS, 10, 50);
    functionICantModify((CALLBACK)myCallback);

}

1
感谢Krab指出非类型模板参数。让我们和编译器玩一个游戏。如果它不喜欢运行时变量,那就在编译时给它一个指针。
void setState(int s) {
    printf("a is: %d\n", s);
}

template <int *n>
void myWrapperss() {
    setState(*n);
}

int a;

void myLogic(void(*CALLBACK)(void)) {
    CALLBACK();
}

int main(int argc, char* argv[]) {

    a = 3;
    myLogic(myWrapperss<&a>);

}

0

这是我的解决方案 :-) 当然它有一些限制。但是对于您的情况,这些限制不应该太重要,因为事件数量在编译时已知。这种方法允许回调函数具有运行时状态。

#include <iostream>

template <int id>
class StatefulCallback {
public:
  typedef void Callback(); 
  static Callback* get(int v) {
    s_state_ = v;
    return callback; 
  }

private:
  static int s_state_;
  static void callback() {
    std::cout << s_state_ << '\n';
  }
};

template <int id>
int StatefulCallback<id>::s_state_ = 0;

int main() {
  const int MAX = 10;

  auto callback_for_event1 = StatefulCallback<1>::get(7);
  auto callback_for_event2 = StatefulCallback<2>::get(9);
  // ...
  auto callback_for_eventMAX = StatefulCallback<MAX>::get(3);

  callback_for_event1();
  callback_for_event2();
  // ...
  callback_for_eventMAX();
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接