如何将类成员函数作为回调传递?

时间:2008-12-30 13:35:35

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

我正在使用一个API,要求我将函数指针作为回调传递。我正在尝试从我的类中使用此API,但是我遇到了编译错误。

以下是我在构造函数中所做的事情:

m_cRedundencyManager->Init(this->RedundencyManagerCallBack);

这不能编译 - 我收到以下错误:

  

错误8错误C3867:'CLoggersInfra :: RedundencyManagerCallBack':函数调用缺少参数列表;使用'& CLoggersInfra :: RedundencyManagerCallBack'创建指向成员的指针

我尝试使用&CLoggersInfra::RedundencyManagerCallBack的建议 - 对我不起作用。

对此有何建议/解释?

我正在使用VS2008。

谢谢!

12 个答案:

答案 0 :(得分:104)

答案 1 :(得分:44)

这不起作用,因为成员函数指针不能像普通函数指针一样处理,因为它需要一个“this”对象参数。

相反,您可以按如下方式传递静态成员函数,就像这方面的普通非成员函数一样:

m_cRedundencyManager->Init(&CLoggersInfra::Callback, this);

该功能可以定义如下

static void Callback(int other_arg, void * this_pointer) {
    CLoggersInfra * self = static_cast<CLoggersInfra*>(this_pointer);
    self->RedundencyManagerCallBack(other_arg);
}

答案 2 :(得分:10)

此答案是对上述评论的回复,不适用于VisualStudio 2008,但应该首选更新的编译器。

同时,您不必再使用空指针,因为std::bindstd::function可用,所以也无需提升。 一个优势(与void指针相比)是类型安全性,因为返回类型和参数是使用std::function明确声明的:

// std::function<return_type(list of argument_type(s))>
void Init(std::function<void(void)> f);

然后,您可以使用std::bind创建函数指针并将其传递给Init:

auto cLoggersInfraInstance = CLoggersInfra();
auto callback = std::bind(&CLoggersInfra::RedundencyManagerCallBack, cLoggersInfraInstance);
Init(callback);

Complete example使用std::bind与成员,静态成员和非成员函数:

#include <functional>
#include <iostream>
#include <string>

class RedundencyManager // incl. Typo ;-)
{
public:
    // std::function<return_type(list of argument_type(s))>
    std::string Init(std::function<std::string(void)> f) 
    {
        return f();
    }
};

class CLoggersInfra
{
private:
    std::string member = "Hello from non static member callback!";

public:
    static std::string RedundencyManagerCallBack()
    {
        return "Hello from static member callback!";
    }

    std::string NonStaticRedundencyManagerCallBack()
    {
        return member;
    }
};

std::string NonMemberCallBack()
{
    return "Hello from non member function!";
}

int main()
{
    auto instance = RedundencyManager();

    auto callback1 = std::bind(&NonMemberCallBack);
    std::cout << instance.Init(callback1) << "\n";

    // Similar to non member function.
    auto callback2 = std::bind(&CLoggersInfra::RedundencyManagerCallBack);
    std::cout << instance.Init(callback2) << "\n";

    // Class instance is passed to std::bind as second argument.
    // (heed that I call the constructor of CLoggersInfra)
    auto callback3 = std::bind(&CLoggersInfra::NonStaticRedundencyManagerCallBack,
                               CLoggersInfra()); 
    std::cout << instance.Init(callback3) << "\n";
}

可能的输出:

Hello from non member function!
Hello from static member callback!
Hello from non static member callback!

此外,使用std::placeholders,您可以动态地将参数传递给回调(例如,如果f具有字符串参数,则可以在return f("MyString");中使用Init。)

答案 3 :(得分:3)

Init采用什么参数?什么是新的错误消息?

C ++中的方法指针有点难以使用。除了方法指针本身,您还需要提供一个实例指针(在您的情况下为this)。也许Init期望它作为一个单独的论点?

答案 4 :(得分:3)

m_cRedundencyManager能否使用会员功能?大多数回调都设置为使用常规函数或静态成员函数。有关详细信息,请参阅C ++ FAQ Lite中的this page

更新:您提供的函数声明显示m_cRedundencyManager期望表单的函数:void yourCallbackFunction(int, void *)。因此,在这种情况下,成员函数作为回调是不可接受的。静态成员函数可以工作,但如果在您的情况下这是不可接受的,则以下代码也可以使用。请注意,它使用来自void *的邪恶演员。


// in your CLoggersInfra constructor:
m_cRedundencyManager->Init(myRedundencyManagerCallBackHandler, this);

// in your CLoggersInfra header:
void myRedundencyManagerCallBackHandler(int i, void * CLoggersInfraPtr);

// in your CLoggersInfra source file:
void myRedundencyManagerCallBackHandler(int i, void * CLoggersInfraPtr)
{
    ((CLoggersInfra *)CLoggersInfraPtr)->RedundencyManagerCallBack(i);
}

答案 5 :(得分:3)

指向类成员函数的指针与指向函数的指针不同。类成员采用隐式额外参数( this 指针),并使用不同的调用约定。

如果您的API需要非成员回调函数,那么您需要传递给它。

答案 6 :(得分:1)

我可以看到init具有以下覆盖:

Init(CALLBACK_FUNC_EX callback_func,void * callback_parm)

其中CALLBACK_FUNC_EX是 typedef void(* CALLBACK_FUNC_EX)(int,void *);

答案 7 :(得分:0)

question and answer中的C++ FAQ Lite涵盖了您的问题以及我认为答案中涉及的考虑因素。我链接的网页上的简短片段:

  

别。

     

因为没有要调用的对象,成员函数就没有意义   它,你不能直接这样做(如果X Window系统是   用C ++重写,它可能会传递对周围对象的引用,   不只是指向功能;这些物体自然会体现出来   所需的功能,可能还有很多)。

答案 8 :(得分:0)

不断创新。
我认为迄今为止的答案还不清楚。

让我们举个例子:

假设您有一个像素数组(ARGB int8_t值数组)

// A RGB image
int8_t* pixels = new int8_t[1024*768*4];

现在您要生成一个PNG。 为此,您可以将函数调用为Jpeg

bool ok = toJpeg(writeByte, pixels, width, height);

其中writeByte是回调函数

void writeByte(unsigned char oneByte)
{
    fputc(oneByte, output);
}

这里的问题:FILE *输出必须是全局变量。
如果您处于多线程环境(例如,http服务器)中,则非常糟糕。

因此,您需要某种方法来使输出成为非全局变量,同时保留回调签名。

想到的立即解决方案是闭包,我们可以使用带有成员函数的类进行模拟。

class BadIdea {
private:
    FILE* m_stream;
public:
    BadIdea(FILE* stream)  {
        this->m_stream = stream;
    }

    void writeByte(unsigned char oneByte){
            fputc(oneByte, this->m_stream);
    }

};

然后做

FILE *fp = fopen(filename, "wb");
BadIdea* foobar = new BadIdea(fp);

bool ok = TooJpeg::writeJpeg(foobar->writeByte, image, width, height);
delete foobar;
fflush(fp);
fclose(fp);

但是,与预期相反,这是行不通的。

原因是,C ++成员函数有点像C#扩展函数那样实现。

所以你有

class/struct BadIdea
{
    FILE* m_stream;
}

static class BadIdeaExtensions
{
    public static writeByte(this BadIdea instance, unsigned char oneByte)
    {
         fputc(oneByte, instance->m_stream);
    }

}

因此,当您要调用writeByte时,不仅需要传递writeByte的地址,还需要传递BadIdea实例的地址。

因此,当您具有writeByte过程的typedef时,它看起来像这样

typedef void (*WRITE_ONE_BYTE)(unsigned char);

您有一个看起来像这样的writeJpeg签名

bool writeJpeg(WRITE_ONE_BYTE output, uint8_t* pixels, uint32_t 
 width, uint32_t height))
    { ... }

从根本上讲,不可能将两个地址的成员函数传递给一个地址的函数指针(不修改writeJpeg),并且没有办法解决。

在C ++中可以做的第二件事是使用lambda函数:

FILE *fp = fopen(filename, "wb");
auto lambda = [fp](unsigned char oneByte) { fputc(oneByte, fp);  };
bool ok = TooJpeg::writeJpeg(lambda, image, width, height);

但是,由于lambda与将实例传递给隐藏类(例如“ BadIdea”类)没有什么不同,因此您需要修改writeJpeg的签名。

lambda相对于手动类的优势在于,您只需要更改一个typedef

typedef void (*WRITE_ONE_BYTE)(unsigned char);

using WRITE_ONE_BYTE = std::function<void(unsigned char)>; 

然后您可以保持其他所有内容不变。

您还可以使用std :: bind

auto f = std::bind(&BadIdea::writeByte, &foobar);

但是,这只是在幕后创建了一个lambda函数,然后还需要更改typedef。

因此,没有办法将成员函数传递给需要静态函数指针的方法。

但是只要您对源代码有控制权,lambdas就是简单的解决方法。
否则,你很不幸。
C ++无法做任何事情。

注意:
std :: function需要#include <functional>

但是,由于C ++也允许您使用C,因此,如果您不介意链接依赖项,则可以在纯C中使用libffcall进行操作。

从GNU下载libffcall(至少在ubuntu上,不要使用发行版提供的包-它已损坏),解压缩。

./configure
make
make install

gcc main.c -l:libffcall.a -o ma

main.c:

#include <callback.h>

// this is the closure function to be allocated 
void function (void* data, va_alist alist)
{
     int abc = va_arg_int(alist);

     printf("data: %08p\n", data); // hex 0x14 = 20
     printf("abc: %d\n", abc);

     // va_start_type(alist[, return_type]);
     // arg = va_arg_type(alist[, arg_type]);
     // va_return_type(alist[[, return_type], return_value]);

    // va_start_int(alist);
    // int r = 666;
    // va_return_int(alist, r);
}



int main(int argc, char* argv[])
{
    int in1 = 10;

    void * data = (void*) 20;
    void(*incrementer1)(int abc) = (void(*)()) alloc_callback(&function, data);
    // void(*incrementer1)() can have unlimited arguments, e.g. incrementer1(123,456);
    // void(*incrementer1)(int abc) starts to throw errors...
    incrementer1(123);
    // free_callback(callback);
    return EXIT_SUCCESS;
}

如果使用CMake,请在add_executable之后添加链接器库

add_library(libffcall STATIC IMPORTED)
set_target_properties(libffcall PROPERTIES
        IMPORTED_LOCATION /usr/local/lib/libffcall.a)
target_link_libraries(BitmapLion libffcall)

或者您可以动态链接libffcall

target_link_libraries(BitmapLion ffcall)

注意:
您可能要包括libffcall标头和库,或者使用libffcall的内容创建一个cmake项目。

答案 9 :(得分:0)

一个简单的解决方案“解决方法”仍然是创建虚拟函数“接口”类,并在调用者类中继承它。然后将其作为您想回叫调用方类的另一个类的参数“可能在构造函数中”传递。

DEFINE接口:

class CallBack 
{
   virtual callMeBack () {};
};

这是您要给您回电的课程:

class AnotherClass ()
{
     public void RegisterMe(CallBack *callback)
     {
         m_callback = callback;
     }

     public void DoSomething ()
     {
        // DO STUFF
        // .....
        // then call
        if (m_callback) m_callback->callMeBack();
     }
     private CallBack *m_callback = NULL;
};

这是将被回调的类。

class Caller : public CallBack
{
    void DoSomthing ()
    {
    }

    void callMeBack()
    {
       std::cout << "I got your message" << std::endl;
    }
};

答案 10 :(得分:0)

指向非静态成员函数的指针的类型不同于指向普通函数的指针的类型。
如果它是普通 static 成员函数,则类型为void(*)(int)
如果它是非静态成员函数,则类型为void(CLoggersInfra::*)(int)
因此,如果希望使用普通的函数指针,则不能将其传递给非静态成员函数。

此外,非静态成员函数对对象具有隐式/隐藏参数。 this指针作为参数隐式传递给成员函数调用。因此,可以通过提供一个对象来仅 调用成员函数。

如果无法更改API Init ,则可以使用调用该成员的包装器函数(普通函数或类静态成员函数)。在最坏的情况下,对象将是包装函数要访问的全局对象。

CLoggersInfra* pLoggerInfra;

RedundencyManagerCallBackWrapper(int val)
{
    pLoggerInfra->RedundencyManagerCallBack(val);
}
m_cRedundencyManager->Init(RedundencyManagerCallBackWrapper);

如果可以更改API Init ,则有很多选择-对象非静态成员函数指针,函数对象,std::function或接口函数。

有关callbacks的不同变化,请参见C++ working examples上的帖子。

答案 11 :(得分:0)

看起来像std::mem_fn(C ++ 11)可以满足您的需求:

功能模板std :: mem_fn为指向成员的指针生成包装对象,该对象可以存储,复制和调用指向成员的指针。调用std :: mem_fn时,可以使用对象的引用和指针(包括智能指针)。