使用好友 class 隐藏私有静态回调方法

Use of a friend class to hide private static callback methods

我需要用一些 API 注册一个 table 函数指针。假设 API 看起来像这样:

void (*FuncPtr)(void*);
void RegisterCallbacks(FuncPtr const (&callbacks)[10], void* context);

因为我正在编写 OOP 代码,所以我在我的 class 中创建静态 "thunk" 方法,这些方法依次调用我的 class 的对象上的方法。我使用 context 指针将指针传递给我的对象。我想在静态初始化时静态分配整个 table 回调指针。所以我最终得到了这个:

class MyClass
{
public:

    static void Thunk (void* context)
    {
        reinterpret_cast<MyClass*>(context)->Method();
    }

    void Method ();

    //
    // More callback thunks and methods...
    //

    MyClass ()
    {
        RegisterCallbacks(s_callbackTable, this);
    }

private:

    static const FuncPtr s_callbackTable[];
}

//
// In the .cpp file:
//
const FuncPtr MyClass::s_callbackTable[] =
{
    &MyClass::Thunk,
    ...
};

这种方法的问题是我需要使 MyClass::Thunk publically 可见,以便静态初始化可以访问它。

编辑:我错了。显然你不需要 public 来做到这一点。

不过我宁愿不要。我可以想到两种方法来解决这个问题。

哪种方法更好?

  1. 创建一个私有静态方法,并使回调table成为方法内部的静态变量。这样我也可以使用指向私有方法的指针来初始化它。从构造函数内部调用私有静态方法以获取 table.

  2. 创建一个包含 public 静态 thunk 方法的单独 class,并使 class 成为 MyClass 的朋友。 Forward declare that class in the header for MyClass, 只是为了允许友元声明。我喜欢这个,因为它还对任何外部消费者完全隐藏了 thunk 方法。不过,我不太确定这样做是否有切实的好处。

    像这样:

    // .h file:
    
    class MyClassInternals
    
    class MyClass
    {
    public:
    
        void Method ();
    
    private:
    
        friend class MyClassInternals;
    
        static const FuncPtr s_callbackTable[];
    }
    
    // .cpp file:
    
    class MyClassInternals
    {
    public:
    
        static void Thunk(void* context)
        {
            reinterpret_cast<MyClass*>(context)->Method();
        }
    }
    
    const FuncPtr MyClass::s_callbackTable[] =
    {
        &MyClassInternals::Thunk,
        ...
    };
    

初始化同一个 class 的成员时,您无需使 Thunk 可见即可使用它。

修复了一些小的语法错误后,您的原始示例在 Thunkprivate 时编译得很好。

https://ideone.com/VmSasp

您可以简单地将所有蹦床机械保留在构造函数实现 (.cpp) 中,无需暴露任何关于接口级别的蹦床 (.h):

MyClass::MyClass() {
    struct trampolines {
        static void method1(void *ctx) { ((MyClass *)ctx)->method1(); }
        static void method2(void *ctx) { ((MyClass *)ctx)->method2(); }
    };
    static void (*callbacks[])(void*) = { trampolines::method1,
                                          trampolines::method2 };
    register_callbacks(callbacks, this);
}

您甚至不需要在 (.h) 中包含使用 callback/context 接口的 C 库的头文件。这更干净,因为您的模块的客户端将不依赖于那些 C 细节。