【问题标题】:Using a C++ class member function as a C callback function使用 C++ 类成员函数作为 C 回调函数
【发布时间】:2010-11-03 07:03:09
【问题描述】:

我有一个 C 库,需要注册一个回调函数来自定义一些处理。回调函数类型为int a(int *, int *)

我正在编写类似于以下的 C++ 代码,并尝试注册一个 C++ 类函数作为回调函数:

class A {
  public:
   A();
   ~A();
   int e(int *k, int *j);
};

A::A()
{
   register_with_library(e)
}

int
A::e(int *k, int *e)
{
  return 0;
}

A::~A() 
{

}

编译器抛出以下错误:

In constructor 'A::A()',
error:
 argument of type ‘int (A::)(int*, int*)’ does not match ‘int (*)(int*, int*)’.

我的问题:

  1. 首先,是否可以像我尝试的那样注册一个 C++ 类成员函数,如果可以,怎么做? (我在http://www.parashift.com/c++-faq-lite/mixing-c-and-cpp.html 阅读了 32.8。但我认为它并不能解决问题)
  2. 是否有替代/更好的方法来解决这个问题?

【问题讨论】:

    标签: c++ c interop callback


    【解决方案1】:

    如果成员函数是静态的,则可以这样做。

    A 类的非静态成员函数有一个class A* 类型的隐式第一个参数,它对应于this 指针。这就是为什么只有在回调的签名也有class A*类型的第一个参数时才能注册它们。

    【讨论】:

    • 是的。该解决方案有效。让我困惑的是编译器没有显示错误 int (A::)(A , int, int*)' does not match ‘int ()(int, int*)'
    • 它确实做到了,但是通过放置 (A::) 这意味着该函数是 A 类的一部分,从那里意味着“this”指针。
    • 我只是好奇...这是标准中规定的吗?我只是浏览了关于类的部分,并没有找到这个。尽管如此,非常有趣。我只是不认为每个编译器都必须以这种方式处理非静态成员函数。
    • @Methos,说成员函数有一个隐式的第一个参数并不意味着这个参数真的存在。这意味着从概念上讲,它就在那里。
    • @Tom,标准称之为“隐式对象参数”,它的类型是 A& 用于非常量成员函数,A const& 用于 const 成员函数,A volatile& 用于 volatile ......等等在。这是一个参考,而“this”是一个指针——主要是因为历史。调用成员函数的对象称为“隐含对象参数”。为了解决重载问题,隐式对象参数被视为隐藏的第一个参数 - 但这一切都只是概念性的,没有什么真正必须存在的
    【解决方案2】:

    如果成员函数不是静态的,你也可以这样做,但它需要更多的工作(另见Convert C++ function pointer to c function pointer):

    #include <stdio.h>
    #include <functional>
    
    template <typename T>
    struct Callback;
    
    template <typename Ret, typename... Params>
    struct Callback<Ret(Params...)> {
       template <typename... Args> 
       static Ret callback(Args... args) {                    
          return func(args...);  
       }
       static std::function<Ret(Params...)> func; 
    };
    
    template <typename Ret, typename... Params>
    std::function<Ret(Params...)> Callback<Ret(Params...)>::func;
    
    void register_with_library(int (*func)(int *k, int *e)) {
       int x = 0, y = 1;
       int o = func(&x, &y);
       printf("Value: %i\n", o);
    }
    
    class A {
       public:
          A();
          ~A();
          int e(int *k, int *j);
    };
    
    typedef int (*callback_t)(int*,int*);
    
    A::A() {
       Callback<int(int*,int*)>::func = std::bind(&A::e, this, std::placeholders::_1, std::placeholders::_2);
       callback_t func = static_cast<callback_t>(Callback<int(int*,int*)>::callback);      
       register_with_library(func);      
    }
    
    int A::e(int *k, int *j) {
       return *k - *j;
    }
    
    A::~A() { }
    
    int main() {
       A a;
    }
    

    这个例子在编译的意义上是完整的:

    g++ test.cpp -std=c++11 -o test
    

    您将需要c++11 标志。在代码中,您看到调用了register_with_library(func),其中func 是动态绑定到成员函数e 的静态函数。

    【讨论】:

    • 酷!我一直想知道如何做到这一点。
    • @Jacko。嗯......那是关于负责堆栈清理的被调用者/调用者,不是吗?我不知道...我忘记了关于 Windows 的一切。 :-)
    • 如何做到这一点是线程安全的?我在这里发布了问题:stackoverflow.com/questions/41198854/…
    • 非常感谢您的回答!
    • @AnnevanRossum 您的解决方案很棒,但我遇到了一个问题,即尝试创建两个这样的回调,而第二个会覆盖第一个。我在stackoverflow.com/q/66474621/2725742 发布了关于像这样分离“静态包装器”所需的最小更改。
    【解决方案3】:

    问题在于方法 != 函数。编译器会将你的方法转换成这样的:

    int e( A *this, int *k, int *j );
    

    所以,你肯定不能传递它,因为类实例不能作为参数传递。一种解决方法是将方法设为静态,这样它就会有好的类型。但它不会有任何类实例,并且可以访问非静态类成员。

    另一种方法是声明一个带有静态指针的函数,该指针指向第一次初始化的 A。该函数仅将调用重定向到类:

    int callback( int *j, int *k )
    {
        static A  *obj = new A();
        a->(j, k);
    }
    

    然后就可以注册回调函数了。

    【讨论】:

    • 什么是 C++ 中的“方法”?这个词在 C++ 标准中从未出现过一次。
    • @Aconcagua,我想你知道,但这里是你问题的答案:stackoverflow.com/questions/8596461/…
    • 函数成员(“方法”)绝对是一个函数。 (确实)有一个附加参数这一事实并不能使它成为一个非函数对象。
    • @AlexisWilke 更重要的是前两个 cmets 到参考答案。此外,第二段(“可互换性”)将暗示“功能!=功能”。乍一看,它可能看起来像分裂的头发,但我不得不以艰难的方式学习它(轻微的误解导致严重的错误)明确定义的重要性。所以推导出两个重要的规则: 1. 不要使用没有明确定义的术语! 2. 不要将新定义与现有定义并行使用。
    • a-&gt;(j, k);,您是否错过了输入e
    【解决方案4】:

    嗯...如果您在 win32 平台上,总会有讨厌的 Thunking 方式...

    Thunking in Win32: Simplifying callbacks to non-static member functions

    这是一个解决方案,但我不建议使用它。
    它有一个很好的解释,很高兴知道它的存在。

    【讨论】:

      【解决方案5】:

      在这个解决方案中,我们有一个模板类 将静态方法作为回调提供给“c函数”。 此类拥有一个“普通”对象(带有一个名为 callback() 的成员函数,最终将被调用)。

      一旦定义了您的类(此处为 A),就可以轻松使用它:

      int main() {
      
        Holder<A> o ( A(23, 23) );
      
        std::cout << o().getN() << "\n";
      
        callACFunctionPtr( fun );
      
        callACFunctionPtr( o.callback );
      
      } // ()
      

      完整示例:

      #include <iostream>
      
      // ----------------------------------------------------------
      // library class: Holder
      // ----------------------------------------------------------
      template< typename HeldObjectType >
      class Holder {
      public:
        static inline HeldObjectType object;
      
        static void callback( ) {
          object.callback();
        } // ()
      
        HeldObjectType &  operator() ( ) {
          return object;
        }
      
        Holder( HeldObjectType && obj )
        {
          object = obj;
        }
      
        Holder() = delete;
      
      }; // class
      
      // ----------------------------------------------------------
      // "old" C function receivin a ptr to function as a callback
      // ----------------------------------------------------------
      using Callback = void (*) (void);
      
      // ..........................................................
      // ..........................................................
      void callACFunctionPtr( Callback f ) {
        f();
      } // ()
      
      // ----------------------------------------------------------
      // ----------------------------------------------------------
      void fun() {
        std::cout << "I'm fun\n";
      } // 
      
      // ----------------------------------------------------------
      // 
      // Common class where we want to write the
      // callback to be called from callACFunctionPtr.
      // Name this function: callback
      // 
      // ----------------------------------------------------------
      class A {
      private:
        int n;
      
      public:
      
        A(  ) : n( 0 ) { }
      
        A( int a, int b ) : n( a+b ) { }
      
        void callback( ) {
          std::cout << "A's callback(): " << n << "\n";
        }
      
        int getN() {
          return n;
        }
      
      }; // class
      
      // ----------------------------------------------------------
      // ----------------------------------------------------------
      int main() {
      
        Holder<A> o ( A(23, 23) );
      
        std::cout << o().getN() << "\n";
      
        callACFunctionPtr( fun );
      
        callACFunctionPtr( o.callback );
      
      } // ()
      

      【讨论】:

        【解决方案6】:

        使用成员函数的问题是它需要一个对象来执行 - 而 C 不知道对象。

        最简单的方法是执行以下操作:

        //In a header file:
        extern "C" int e(int * k, int * e);
        
        //In your implementation: 
        int e(int * k, int * e) { return 0; }
        

        【讨论】:

        • 所以你的意思是不要让它成为一个成员函数?
        • 在这种情况下,是的。 IMO 使用独立函数所提供的更大的简单性超过了所涉及的封装不足。
        • 这是假设他的e函数不需要访问this
        猜你喜欢
        • 2017-05-03
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多