ofe*_*fer 66 c++ function-pointers callback c++03
我正在使用一个API,要求我将函数指针作为回调传递.我正在尝试从我的类中使用此API,但是我遇到了编译错误.
这是我从构造函数中做的:
m_cRedundencyManager->Init(this->RedundencyManagerCallBack);
Run Code Online (Sandbox Code Playgroud)
这不编译 - 我收到以下错误:
错误8错误C3867:'CLoggersInfra :: RedundencyManagerCallBack':函数调用缺少参数列表; 使用'&CLoggersInfra :: RedundencyManagerCallBack'创建指向成员的指针
我尝试使用这个建议&CLoggersInfra::RedundencyManagerCallBack
- 对我不起作用.
对此有何建议/解释?
我正在使用VS2008.
谢谢!!
Jos*_*vin 110
这是一个简单的问题,但答案非常复杂.简短的回答是你可以做你正在尝试用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!
Run Code Online (Sandbox Code Playgroud)
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);
Run Code Online (Sandbox Code Playgroud)
回到你的例子,现在有一个明显的问题.'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.这基本上让编译器为您生成相同的东西.
Joh*_*itb 48
正如您现在所示,您的init函数接受非成员函数.所以这样做:
m_cRedundencyManager->Init(&CLoggersInfra::Callback, this);
Run Code Online (Sandbox Code Playgroud)
并调用Init
static void Callback(int other_arg, void * this_pointer) {
CLoggersInfra * self = static_cast<CLoggersInfra*>(this_pointer);
self->RedundencyManagerCallBack(other_arg);
}
Run Code Online (Sandbox Code Playgroud)
这是因为指向静态成员函数的函数指针不是成员函数指针,因此可以像处理自由函数的指针一样处理.
Roi*_*ton 11
这个答案是对上述评论的回复,不适用于VisualStudio 2008,但应该首选更新的编译器.
与此同时,您不必再使用空指针,也不需要提升,因为std::bind
它std::function
是可用的.一个优点(与void指针相比)是类型安全性,因为返回类型和参数是使用std::function
以下方式明确声明的:
// std::function<return_type(list of argument_type(s))>
void Init(std::function<void(void)> f);
Run Code Online (Sandbox Code Playgroud)
然后你可以创建函数指针std::bind
并将其传递给Init:
auto cLoggersInfraInstance = CLoggersInfra();
auto callback = std::bind(&CLoggersInfra::RedundencyManagerCallBack, cLoggersInfraInstance);
Init(callback);
Run Code Online (Sandbox Code Playgroud)
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";
}
Run Code Online (Sandbox Code Playgroud)
可能的输出:
Hello from non member function!
Hello from static member callback!
Hello from non static member callback!
Run Code Online (Sandbox Code Playgroud)
此外,使用std::placeholders
您可以动态地将参数传递给回调(例如return f("MyString");
,Init
如果f具有字符串参数,则可以使用in ).
小智 7
将 C 风格的回调函数与 C++ 类实例连接起来仍然很困难。我想重新表述一下原来的问题:
您正在使用的某些库需要从该库回调 C 风格函数。更改库 API 是不可能的,因为它不是您的API。
您希望在您自己的 C++ 代码中的成员方法中处理回调
由于您没有(确切地)提到您想要处理什么回调,我将给出一个使用GLFW 回调进行按键输入的示例。(旁注:我知道 GLFW 提供了一些其他机制将用户数据附加到他们的 API,但这不是这里的主题。)
我不知道这个问题的任何解决方案不包括使用某种静态对象。让我们看看我们的选择:
简单的方法:使用C风格的全局对象
正如我们总是在类和实例中思考的那样,我们有时会忘记,在 C++ 中,我们仍然拥有 C 的全部武器库。所以有时这个非常简单的解决方案不会浮现在脑海中。
假设我们有一个类Presentation应该处理键盘输入。这可能看起来像这样:
struct KeyInput {
int pressedKey;
} KeyInputGlobal;
void globalKeyHandler(GLFWwindow* window, int key, int scancode, int action, int mods) {
KeyInputGlobal.pressedKey = key;
}
int Presentation::getCurrentKey()
{
return KeyInputGlobal.pressedKey;
}
void Presentation::initGLFW()
{
glfwInit();
glfwSetKeyCallback(window, globalKeyHandler);
}
Run Code Online (Sandbox Code Playgroud)
我们有一个全局对象KeyInputGlobal应该接收按下的键。函数globalKeyHandler具有 GLFW 库调用我们的代码所需的 C 风格 API 签名。它在我们的成员方法initGLFW上激活。如果我们代码中的任何地方对当前按下的键感兴趣,我们可以调用另一个成员方法Presentation::getCurrentKey
这种方法有什么问题吗?
也许一切都很好。完全取决于您的用例。也许您完全可以在应用程序代码中读取最后按下的键。您不关心错过按键事件。您所需要的就是简单的方法。
概括来说:如果您能够在 C 风格代码中完全处理回调,计算一些结果并将其存储在全局对象中以便稍后从代码的其他部分读取,那么使用这种简单的方法可能确实有意义。好的一面是:它非常容易理解。不足之处?这感觉有点像作弊,因为你并没有真正在 C++ 代码中处理回调,你只是使用了结果。如果您将回调视为一个事件并希望在您的成员方法中正确处理每个事件,则此方法还不够。
另一种简单的方法:使用 C++ 静态对象
我想我们很多人已经这样做了。我当然有。思考:等等,我们有一个全局变量的 C++ 概念,那就是使用static。但我们可以在这里简短地讨论一下:它可能比使用前面示例中的 C 风格更具 C++ 风格,但问题是相同的 - 我们仍然有全局变量,很难与非静态、常规成员方法结合在一起。为了完整起见,我们的类声明如下所示:
class Presentation
{
public:
struct KeyInput {
int pressedKey;
};
static KeyInput KeyInputGlobal;
static void globalKeyHandler(GLFWwindow* window, int key, int scancode, int action, int mods) {
KeyInputGlobal.pressedKey = key;
}
int getCurrentKey()
{
return KeyInputGlobal.pressedKey;
}
...
}
Run Code Online (Sandbox Code Playgroud)
激活回调看起来是一样的,但我们还必须定义在实现中接收按下的键的静态结构:
void Presentation::initGLFW()
{
glfwInit();
glfwSetKeyCallback(window, globalKeyHandler);
}
//static
Presentation::KeyInput Presentation::KeyInputGlobal;
Run Code Online (Sandbox Code Playgroud)
您可能倾向于从我们的回调方法globalKeyHandler中删除static关键字:编译器会立即告诉您不能再将其传递给glfwSetKeyCallback()中的 GLFW 。现在,如果我们只能以某种方式将静态方法与常规方法连接起来......
使用静态和 lambda 的 C++11 事件驱动方法
我能找到的最佳解决方案如下。它有效并且有点优雅,但我仍然不认为它是完美的。我们来看看并讨论一下:
void Presentation::initGLFW()
{
glfwInit();
static auto callback_static = [this](
GLFWwindow* window, int key, int scancode, int action, int mods) {
// because we have a this pointer we are now able to call a non-static member method:
key_callbackMember(window, key, scancode, action, mods);
};
glfwSetKeyCallback(window,
[](GLFWwindow* window, int key, int scancode, int action, int mods)
{
// only static methods can be called here as we cannot change glfw function parameter list to include instance pointer
callback_static(window, key, scancode, action, mods);
}
);
}
void Presentation::key_callbackMember(GLFWwindow* window, int key, int scancode, int action, int mods)
{
// we can now call anywhere in our code to process the key input:
app->handleInput(key, action);
}
Run Code Online (Sandbox Code Playgroud)
callback_static的定义是我们将静态对象与实例数据连接的地方,在本例中,这是我们的Presentation类的实例。您可以如下阅读该定义: 如果在此定义之后的任何时间调用callback_static ,则所有参数都将传递给刚刚使用的Presentation实例中调用的成员方法key_callbackMember。这个定义与 GLFW 库无关——它只是为下一步做准备。
现在,我们使用第二个 lambda 在glfwSetKeyCallback()中的库中注册我们的回调。同样,如果没有将callback_static定义为静态,我们就无法将其传递给GLFW。
这是所有初始化之后运行时 GLFW 调用我们的代码时发生的情况:
好处:我们已经实现了我们想要的目标,无需在初始化方法initGLFW之外定义全局对象。不需要 C 风格的全局变量。
坏处:不要仅仅因为所有内容都整齐地打包到一种方法中而被愚弄。我们仍然有静态对象。全局对象所面临的所有问题也随之而来。例如,多次调用我们的初始化方法(使用不同的Presentation实例)可能不会达到您想要的效果。
概括
可以将现有库的 C 风格回调连接到您自己的代码中的类实例。您可以尝试通过在代码的成员方法中定义必要的对象来尽量减少管理代码。但每个回调仍然需要一个静态对象。如果您想使用 C 样式回调连接 C++ 代码的多个实例,请准备好引入比上面示例更复杂的静态对象管理。
希望这对某人有帮助。快乐编码。
归档时间: |
|
查看次数: |
96589 次 |
最近记录: |