【问题标题】:How to avoid replicating callback functions (C++)如何避免复制回调函数(C++)
【发布时间】:2015-04-28 16:20:43
【问题描述】:

我使用的 API 只接受 void 回调函数:

void (* CALLBACKFUNC) (void);

我想用参数调用回调函数,而不是为不同的输入参数编写多个具有相同功能的回调函数。 假设我需要一个像

这样的回调函数

void myFunc (int a);

UPDATE:更多信息:调用回调函数,基于事件应该是这样的:

event1 -> 调用 myFunc(1);
event2 -> 调用 myFunc(2);
...

事件的数量是有限的,并且 MAX 是预先定义的(如果有帮助的话),但我不想复制该功能(实际上,在真实情况下,有多个输入值,复制不同组合的函数调用并非易事)

PS:我也可以使用 C++11。有什么建议?。

【问题讨论】:

  • 你试过什么?请向我们展示一些(相关的)代码。如果您有错误,请同时显示。
  • 我试图传递一个仿函数而不是函数指针,显然我得到了错误:无法将参数 x 从 'FUNCTOR_TYPE' 转换为 'CALLBACKFUNC'
  • 您可以使用std::functionstd::bind 自定义回调函数。这些是 C++11 特性。
  • 您是在设计 API 还是在寻找与现有 API 集成的最佳方式?
  • @TOWI_Parallelism:如果你不能改变 API,那么你就不能有状态回调。但是,您可以拥有所有使用共享功能的无状态回调,这对于您的情况可能已经足够了。

标签: c++ c++11 callback function-pointers


【解决方案1】:

这个解决方案怎么样?您无需手动定义新函数来设置不同的状态。

#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:这是真的,所以不接受它,所以其他人认为这个问题没有得到回答。我会考虑的。
【解决方案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!我实际上对您的思考和编码速度感到惊讶!我只能接受一个答案!我想我必须去找螃蟹的答案。
【解决方案3】:

感谢 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>);

}

【讨论】:

    【解决方案4】:

    有一个解决方案,但它更像是一种 hack 而不是 独立于平台。它仅适用于 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);
    
    }
    

    【讨论】:

      【解决方案5】:

      这是我的解决方案 :-) 当然它有局限性。但是对于在编译时已知事件数量的情况,这些限制应该无关紧要。这种方法允许回调的运行时状态。

      #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();
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2016-06-24
        • 2017-02-09
        • 1970-01-01
        • 1970-01-01
        • 2022-01-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多