【问题标题】:C++ Using Class Method as a Function Pointer TypeC++ 使用类方法作为函数指针类型
【发布时间】:2010-12-16 20:03:24
【问题描述】:

在 C 库中,有一个函数等待函数指针,这样:

lasvm_kcache_t* lasvm_kcache_create(lasvm_kernel_t kernelfunc, void *closure)

其中 lasvm_kernel_t 定义为:

typedef double (*lasvm_kernel_t)(int i, int j, void* closure);

现在,如果我将类中定义的方法发送到 lasvm_kcache_create:

double cls_lasvm::kernel(int i, int j, void *kparam)
...
lasvm_kcache_t *kcache=lasvm_kcache_create(&kernel, NULL);

我得到:“无法将 'double (cls_lasvm::)(int, int, void)' 转换为 'double ()(int, int, void) '"

我该怎么办?

【问题讨论】:

    标签: c++ function-pointers


    【解决方案1】:

    我假设closure 参数是一个上下文“cookie”,用于使用回调获取适当的上下文。这是回调函数的一个惯用语,根据您提供的 sn-ps 似乎是正在发生的事情(但我不确定,因为我对kcache_create() 一无所知,除了你在这里发布)。

    您可以使用该 cookie 将指针传递给您正在处理的 cls_lasvm 实例,如下所示:

    extern "C"
    double
    lasvm_kcache_create_callback( int i, int j, void* closure)
    {
        // have to get a cls_lasvm pointer somehow, maybe the 
        // void* clpsure is a context value that can hold the
        // this pointer - I don't know
    
        cls_lasvm* me = reinterpret_cast<cls_lasvm*>( closure);
    
        return me->kernel( i, j)
    
    }
    
    
    class cls_lasvm //...
    {
    
        ...
    
        // the callback that's in the class doens't need kparam
        double cls_lasvm::kernel(int i, int j);
    
    };
    
    ...
    
    // called like so, assuming it's being called from a cls_lasvm
    //  member function
    
    lasvm_kcache_t *kcache=lasvm_kcache_create(&lasvm_kcache_create_callback, this);
    

    如果我错认为闭包是上下文 cookie,那么您在 cls_lasvm 类中的回调函数需要是静态的:

    extern "C"
    double
    lasvm_kcache_create_callback( int i, int j, void* closure)
    {
        // if there is no context provided (or needed) then
        // all you need is a static function in cls_lasvm
    
        return cls_lasvm::kernel( i, j, closure);
    }
    
    // the callback that's in the class needs to be static
    static double cls_lasvm::kernel(int i, int j, void* closure);
    

    请注意,在 C++ 中实现的 C 回调函数必须为extern "C"。它可能看起来像一个类中的静态函数,因为类静态函数通常使用与 C 函数相同的调用约定。但是,这样做是一个等待发生的错误(请参阅下面的 cmets),所以请不要 - 改为使用 extern "C" 包装器。

    如果closure 不是上下文cookie,并且由于某种原因cls_lasvm::kernel() 不能是静态的,那么您需要想出一种方法将this 指针存储在某处并在@987654332 中检索该指针@函数,类似于我在第一个示例中所做的方式,只是指针必须来自您自己设计的某种机制。请注意,这可能会使使用 lasvm_kcache_create() 不可重入和非线程安全。这可能是也可能不是问题,具体取决于您的具体情况。

    【讨论】:

    • 不幸的是,我被静态方法所困扰,它的调用约定与“C”函数不同。考虑到 C+= 编译器的移动方式,我怀疑这是否会保持不变。高度优化的编译器将利用所有可能的技术来提高速度,因此在寄存器而不是堆栈中传递参数将开始变得越来越普遍(这只是时间问题)。
    • @Martin:好的 - 我将删除它可能是安全的建议。伙计 - 如果/当这些优化(或其他)变得司空见惯时,会有 很多 代码会中断。我猜超过 75% 的时间我看到 C++ 代码中使用的 C 回调是直接通过静态成员函数完成的。
    • @Michael:我时不时地在年轻的程序员身上看到它。但是在大公司中,这些错误总是在代码审查时引起咳嗽(一旦咳嗽,错误就不会再犯,因为我们会让它变得非常尴尬)。所以我在开源项目中看到(不是很多)聪明的头脑不会抨击年轻一代。
    • @Martin - 我猜你不会处理很多 Windows 代码(就像我没有看到很多 Unix 代码一样) - 我一直在那里看到它。如果 MSVC 曾经使用静态成员函数作为 C 回调中断,我会感到非常惊讶。
    【解决方案2】:

    每个 C++ 成员函数都有一个隐含的、隐藏的第一个参数 this。

    所以double cls_lasvm::kernel(int i, int j, void* kparam)的方法真的是: double cls_lasvm::kernel(cls_lasvm* this, int i, int j, void* kparam),将其用作函数指针参数是不合适/不可能的。

    要取得进展,请将您的方法转换为静态成员方法。这将删除 this 指针。您可能还有其他问题需要克服,但这是一个好的开始。

    【讨论】:

    • 我不认为它有一个隐含的隐藏“第一个参数”:你不妨说它有一个隐含的隐藏“最后一个参数”。 this 的值在被调用者中可用,仅此而已。所以这不是你所说的“真的” - 尝试将kernelfunc 更改为该类型,代码仍然无法编译。指向函数的指针和指向成员函数的指针是不同的东西。
    • @Steve:我认为它通常被定义为第一个参数,实际上。这种区别确实会产生影响,因为其他函数(我想还有其他语言)需要知道传递这些参数的顺序。
    • 不要对 C 回调方法使用静态成员方法。由于 C++ 标准故意不指定 ABI,因此无法保证它可以正常工作。
    • @Steve:当然,解释不是 100% 准确,但它是一个很好的简化,并且对应于(几乎?)每个成员函数签名的实际实现。
    • @Konrad:我同意史蒂夫的观点。过于简单化了;它暗示了实际上并不存在的等效性。主要使用堆栈进行参数传递的 Visual C++(32 位)将寄存器用于非静态成员函数的 this 指针。这意味着它在参数列表的开头或结尾都不是一个额外的参数,它只是一个额外的参数。这也意味着无法将其转换为静态或非成员函数并能够正确调用它。
    【解决方案3】:

    如果它是一个您无法修改其代码的外部 C 库,那么您无能为力。您将无法调用成员函数,因为它们需要 this 指针才能正常工作(以获取对象的属性)。我能想到的最简单的解决方法是使用第三个void* 参数来传递this 指针。您可以在定义另外一个 typedef 之后定义 struct like:

    typedef double (cls_lasvm::*lasvm_kernel_t_member)(int i, int j, void* closure);
    
    
    struct MyParam
    {
       A* pThis;
       lasvm_kernel_t_member pMemFun;
       void* kParam;
    };
    

    我还没有编译,希望有道理。

    然后在你的类中定义一个接收库调用的静态方法:

    class cls_lasvm
    {
      static double test(int i, int j, void *kparam)
      {
        MyParam* pParam = reinterpret_cast<MyParam*>(kparam);
        return (pParam->*pMemFun)(i,j,pParam->kParam);
      }
    };
    

    打电话时你应该使用类似的东西:

    cls_lasvm a;
    MyParam param;
    param.pThis = &a;
    param.pMemFun = &cls_lasvm::kernel;
    param.kParam = NULL;
    
    lasvm_kcache_create(&cls_lasvm::test,&a);
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-10-10
      • 2012-10-22
      • 1970-01-01
      • 1970-01-01
      • 2021-12-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多