当前位置:  开发笔记 > 编程语言 > 正文

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

如何解决《如何将类成员函数作为回调传递?》经验,为你挑选了3个好方法。

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

这是我从构造函数中做的:

m_cRedundencyManager->Init(this->RedundencyManagerCallBack);

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

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

我尝试使用这个建议&CLoggersInfra::RedundencyManagerCallBack- 对我不起作用.

对此有何建议/解释?

我正在使用VS2008.

谢谢!!



1> Joseph Garvi..:

这是一个简单的问题,但答案非常复杂.简短的回答是你可以做你正在尝试用std :: bind1st或boost :: bind做的事情.答案越长越好.

编译器建议您使用&CLoggersInfra :: RedundencyManagerCallBack是正确的.首先,如果RedundencyManagerCallBack是一个成员函数,则该函数本身不属于CLoggersInfra类的任何特定实例.它属于类本身.如果您之前曾调用过静态类函数,您可能已经注意到使用了相同的SomeClass :: SomeMemberFunction语法.由于函数本身是"静态的",因为它属于类而不是特定的实例,因此使用相同的语法.'&'是必要的,因为从技术上讲你不直接传递函数 - 函数不是C++中的真实对象.相反,你在技术上传递函数的内存地址,即指向函数指令在内存中开始的指针.

但在这种情况下,这只是问题的一半.正如我所说,RedundencyManagerCallBack该函数不属于任何特定实例.但听起来你想把它作为一个特定实例的回调传递给它.要了解如何执行此操作,您需要了解成员函数的真正含义:常规未定义的任何类函数以及额外的隐藏参数.

例如:

class A {
public:
    A() : data(0) {}
    void foo(int addToData) { this->data += addToData; }

    int data;
};

...

A an_a_object;
an_a_object.foo(5);
A::foo(&an_a_object, 5); // This is the same as the line above!
std::cout << an_a_object.data; // Prints 10!

A :: foo需要多少个参数?通常我们会说1.但是在幕后,foo真的需要2.看看A :: foo的定义,它需要一个特定的A实例,以便'this'指针有意义(编译器需要知道什么'这是).通常通过语法MyObject.MyMemberFunction()指定您想要的"this"的方式.但这只是将MyObject的地址作为第一个参数传递给MyMemberFunction的语法糖.类似地,当我们在类定义中声明成员函数时,我们不会在参数列表中放入"this",但这只是语言设计者提供的一种保存输入的礼物.相反,你必须指定一个成员函数是静态的,以选择退出它自动获得额外的'this'参数.

struct A {
    int data;
};

void a_init(A* to_init)
{
    to_init->data = 0;
}

void a_foo(A* this, int addToData)
{ 
    this->data += addToData;
}

...

A an_a_object;
a_init(0); // Before constructor call was implicit
a_foo(&an_a_object, 5); // Used to be an_a_object.foo(5);

回到你的例子,现在有一个明显的问题.'Init'想要一个指向一个带一个参数的函数的指针.但是&CLoggersInfra :: RedundencyManagerCallBack是一个指向函数的指针,它接受两个参数,它是普通参数和秘密'this'参数.因此,为什么你仍然会遇到编译器错误(作为旁注:如果你曾经使用过Python,那么这种混淆就是为什么所有成员函数都需要'self'参数).

处理此问题的详细方法是创建一个特殊对象,该对象包含指向所需实例的指针,并且具有一个名为"run"或"execute"(或重载"()"运算符)的成员函数,该函数接受参数对于成员函数,只需在存储的实例上使用这些参数调用成员函数.但这需要你改变'Init'来取你的特殊对象而不是原始的函数指针,听起来像Init是别人的代码.每次出现这个问题时都要制作一个特殊的类会导致代码膨胀.

所以现在,最后,好的解决方案,boost :: bind和boost :: function,你可以在这里找到每个文档:

boost :: bind docs, boost :: function docs

boost :: bind将允许你获取一个函数,并为该函数提供一个参数,并在该参数被"锁定"的地方创建一个新函数.所以,如果我有一个添加两个整数的函数,我可以使用boost :: bind来创建一个新函数,其中一个参数被锁定为5.这个新函数只接受一个整数参数,并且总是会添加5个它.使用这种技术,你可以将隐藏的'this'参数"锁定"为特定的类实例,并生成一个只接受一个参数的新函数,就像你想要的那样(注意隐藏的参数总是第一个)参数,正常参数在它之后按顺序排列).查看boost :: bind文档中的示例,他们甚至专门讨论将其用于成员函数.从技术上讲,你可以使用一个名为std :: bind1st的标准函数,但boost :: bind更通用.

当然,还有一个问题.boost :: bind会为你做一个很好的boost :: function,但这在技术上仍然不像Init可能想要的原始函数指针.值得庆幸的是,升压提供了一种方法来转换的boost ::功能的原始指针,在计算器上的记录在这里.它如何实现这一点超出了这个答案的范围,尽管它也很有趣.

不要担心,如果这看起来很荒谬 - 你的问题与C++的几个黑暗角落相交,而且一旦你学会了它,boost :: bind就非常有用了.

C++ 11更新:您现在可以使用捕获'this'的lambda函数代替boost :: bind.这基本上让编译器为您生成相同的东西.


在答案的早期,std :: bind1st被建议作为实现解决方案的一种方式,但答案的后半部分仅仅是在boost :: bind方面.如何使用std :: bind1st?
这应该是公认的答案!如果您无法更改库或传递可选参数,则接受的答案实际上不起作用.
这是一个很好的答案!

2> Johannes Sch..:

正如您现在所示,您的init函数接受非成员函数.所以这样做:

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

并调用Init

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

这是因为指向静态成员函数的函数指针不是成员函数指针,因此可以像处理自由函数的指针一样处理.



3> Roi Danton..:

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


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

// std::function
void Init(std::function f);

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

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

std::bind与成员,静态成员和非成员函数一起使用的完整示例:

#include 
#include 
#include 

class RedundencyManager // incl. Typo ;-)
{
public:
    // std::function
    std::string Init(std::function 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您可以动态地将参数传递给回调(例如return f("MyString");,Init如果f具有字符串参数,则可以使用in ).

推荐阅读
地之南_816
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有