【问题标题】:C++: static function wrapper that routes to member function?C++:路由到成员函数的静态函数包装器?
【发布时间】:2009-11-10 12:22:30
【问题描述】:

我尝试了各种设计方法来解决这个问题,但我似乎无法做到正确。

我需要公开一些静态函数以用作 C 库的回调函数。但是,我希望实际实现是非静态的,因此我可以使用虚函数并在基类中重用代码。如:

class Callbacks {
  static void MyCallBack() { impl->MyCallBackImpl(); }
  ...

class CallbackImplBase {
   virtual void MyCallBackImpl() = 0;

但是我尝试解决这个问题(单例,通过让回调包含在实现类中的组合等)我最终陷入了死胡同(impl 通常最终指向基类,而不是派生类)。

我想知道这是否可能,或者我是否坚持创建某种辅助函数而不是使用继承?

【问题讨论】:

  • 添加更多代码。我还不能理解这个问题。
  • 我不明白:为什么你的回调必须是静态的?另外,请注意,C lib 不会接受指向成员函数的指针(这与指向函数的指针不同),因为它不知道成员函数是什么。
  • @Julien:因为它们要从 C 中调用,因此不能成为成员函数......正如你自己所说的那样:)。
  • @Frank:我不知道你可以将一个指向静态成员的指针传递给需要指向非成员函数的指针......很高兴你回答了我!
  • 将指针传递给静态成员函数来代替函数指针通常会起作用,但不能保证会起作用。请参阅此问题以了解如何正确执行此操作。基本上你需要使用一个非成员函数。 stackoverflow.com/questions/499153/…

标签: c++ design-patterns callback


【解决方案1】:

问题一:

虽然它看起来和似乎可以在您的设置上运行,但由于未定义 C++ ABI,因此不能保证它可以正常工作。所以从技术上讲,你不能使用 C++ 静态成员函数作为 C 代码使用的函数指针。

问题2:

所有 C callacks(据我所知)都允许您将用户数据作为 void* 传回。您可以将其用作指向具有虚拟方法的对象的指针。 但是您必须确保在将其转换为 void* 之前将 dynamic_cast() 用于基类(在回调中使用虚方法的基类),否则另一端的指针可能无法正确解释(尤其是涉及多重继承时)。

问题3:

异常:C 不是为处理异常而设计的(尤其是带有回调的旧 C 库)。所以不要指望逃避你的回调的异常为调用者提供任何有意义的东西(它们更有可能导致应用程序终止)。

解决办法:

你需要做的是使用 extern "C" 函数作为回调函数,它调用已知类型对象的虚方法并抛出所有异常。

C pthread 例程的示例

#include <iostream>

extern "C" void* start_thread(void* data);

class Work
{
    public:
    virtual ~Work() {}
    virtual void doWork() = 0;
};

/*
 * To be used as a callback for C code this MUST be declared as
 * with extern "C" linkage to make sure the calling code can
 * correctly call it
 */
void* start_thread(void* data)
{
    /*
     * Use reinterpret_cast<>() because the only thing you know
     * that you can do is cast back to a Work* pointer.
     *
     */
    Work*  work = reinterpret_cast<Work*>(data);
    try
    {
        work->doWork();
    }
    catch(...)
    {
        // Never let an exception escape a callback.
        // As you are being called back from C code this would probably result
        // in program termination as the C ABI does not know how to cope with
        // exceptions and thus would not be able to unwind the call stack.
        //
        // An exception is if the C code had been built with a C++ compiler
        // But if like pthread this is an existing C lib you are unlikely to get
        // the results you expect.
    }
    return NULL;
}

class PrintWork: public Work
{
    public:
    virtual void doWork()
    {
        std::cout << "Hi \n";
    }
};

int main()
{
    pthread_t   thread;
    PrintWork   printer;
    /*
     * Use dynamic_cast<>() here because you must make sure that
     * the underlying routine receives a Work* pointer
     * 
     * As it is working with a void* there is no way for the compiler
     * to do this intrinsically so you must do it manually at this end
     */
    int check = pthread_create(&thread,NULL,start_thread,dynamic_cast<Work*>(&printer));
    if (check == 0)
    {
        void*   result;
        pthread_join(thread,&result);
    }
}


    

【讨论】:

  • 为什么要使用 reinterpret_cast 而不是 static_cast 来让 Work* 脱离 void*?
  • 我发现这正是 reinterpret_cast 设计的确切情况(将指针转换为 void* (静态转换旨在转换为相似类型))。它也是一种文档:- reinterpret_cast 修改此代码时要非常小心。
【解决方案2】:

这是可能的。也许您在如何初始化具体实现方面存在问题?

事实上,我记得有一个库与此非常相似。您可能会发现查看libxml++ 源代码很有用。它建立在libxml 之上,这是一个 C 库。

libxml++ 使用静态函数结构来处理回调。对于定制,该设计允许用户(通过虚拟函数)提供他/她自己的实现,然后将回调转发到该实现。我想这几乎就是你的情况。

【讨论】:

    【解决方案3】:

    如下所示。单例在 Callback 类中,Instance 成员将返回一个静态分配的对 CallbackImpl 类的引用。这是一个单例,因为引用只会在第一次调用函数时初始化一次。此外,它必须是引用或指针,否则虚函数将不起作用。

    class CallbackImplBase
    {
    public:
       virtual void MyCallBackImpl() = 0;
    };
    
    class CallbackImpl : public CallbackImplBase
    {
    public:
        void MyCallBackImpl()
        {
            std::cout << "MyCallBackImpl" << std::endl;
        }
    };
    
    class Callback
    {
    public:
        static CallbackImplBase & Instance()
        {
            static CallbackImpl instance;
            return instance;
        }
    
        static void MyCallBack()
        {
            Instance().MyCallBackImpl();
        }
    };
    
    extern "C" void MyCallBack()
    {
        Callback::MyCallBack();
    }
    

    【讨论】:

    • 啊,但我确实使用了 extern "C" 函数作为实际回调,我只是使用类来保存单例。
    【解决方案4】:

    是否有任何参数传递给用户定义的回调函数?有什么方法可以将用户定义的值附加到传递给这些回调的数据上?我记得当我为 Win32 窗口实现包装库时,我使用 SetWindowLong()this 指针附加到窗口句柄,稍后可以在回调函数中检索该指针。基本上,您需要在某处打包this 指针,以便在触发回调时检索它。

    struct CALLBACKDATA
    {
      int field0;
      int field1;
      int field2;
    };
    
    struct MYCALLBACKDATA : public CALLBACKDATA
    {
      Callback* ptr;
    };
    
    
    registerCallback( Callback::StaticCallbackFunc, &myCallbackData, ... );
    
    void Callback::StaticCallbackFunc( CALLBACKDATA* pData )
    {
      MYCALLBACKDATA* pMyData = (MYCALLBACKDATA*)pData;
      Callback* pCallback = pMyData->ptr;
    
      pCallback->virtualFunctionCall();
    }
    

    【讨论】:

    • 用户数据没有专用参数。我可以传入一个结构,但我不知道它是否在 lib 中以某种方式使用,因此我希望它像今天一样保持一个 PODS(如果我使用虚函数等就不会这样)。
    • 如果你传入了一个结构体(假设你传递了一个指向该结构体的指针),你可以从这个结构体派生并添加一个新的Callbacks* 字段。将一些额外的数据附加到结构的末尾时,lib 并不明智。
    • 您不能将静态成员函数用作回调的 C 函数指针。 C++ ABI 没有定义,尽管这可能适用于您的编译器,但编译器不保证。
    • 如果您担心,请为静态成员回调指定显式调用约定。
    • @Evan:在标准 C++ 中没有办法做到这一点。一些编译器具有特定的编译器特定技巧,但这也是不可移植的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-09-10
    • 1970-01-01
    相关资源
    最近更新 更多