类成员函数指针

3
关于在 C++ 中的函数指针,类成员函数和全局函数有什么区别?我问这个问题是因为 Windows 的 CreateThread 方法似乎不接受作为线程代码的函数如果是一个类成员。当该函数是全局方法时,我可以将其传递给 CreateThread 消息,但一旦我将其作为类的成员,就会出现错误“类型为 [方法布局] 的参数与类型为 LPTHREAD_START_ROUTINE 的参数不兼容”。现在 ClassName::* 在中间,这是否影响了它?
如何解决这个问题?

1
C++11中的lambda表达式或者在此之前使用boost::bind - 发布有问题的代码,我们会向您展示如何解决。 - doctorlove
可能的线程函数的签名必须完全匹配,Win32-API 是一个纯 C API,不适用于与 C++ 类一起使用。 - bash.d
@doctorlove,该结果与CreateThread所使用的函数指针不兼容。 - chris
1
在C++11中,您可能考虑切换到标准线程库。然后,它类似于thread([this]{member();}),而不是正确答案中的胡言乱语,具有可移植性作为奖励。 - Mike Seymour
@MikeSeymour,说得好,我不知道为什么这个没有想到。我会把它放在显眼的位置。 - chris
2个回答

5

成员函数指针(DWORD(WINAPI Foo::*)(LPVOID))与函数指针(DWORD(WINAPI *)(LPVOID))是不同的类型。成员函数有一个隐藏的this参数,导致签名不匹配。

最简单的方法是使用C++11的<thread>头文件:

struct Foo {
    void threadProc() {}
};

int main() {
    Foo foo;
    std::thread t{&Foo::threadProc, foo, /*other arguments to threadProc*/};
    t.join();
}

如果必须使用CreateThread,请利用void *参数传递实例:
struct Foo {
    DWORD threadProc() {...}
};

extern "C" DWORD WINAPI proxyThreadProc(LPVOID userData) {
    auto foo = static_cast<Foo *>(userData);
    if (foo) {foo->threadProc();}
}

int main() {
    Foo foo;
    CreateThread(..., proxyThreadProc, &foo, ...);
}

现在你的类可以是任何你想要的类型(例如 std::function),只要在代理过程中使用正确的参数调用即可正常工作。


1
值得注意的是,如果proxyThreadProcFoo类的静态成员函数,这种方法也同样适用。在这种情况下,有时使用私有成员函数是一个不错的方法。 - Magnus Hoff
在函数后面,'extern "C"' 不应该跟着 '{' 和 '}' 吗? - Kevin
@MagnusHoff,对我来说也是比较新的,但是阅读这篇文章可能会有所帮助。 - chris
1
@Kevin,括号只是为了将多个内容包含在一起而必要的。 - chris
@chris 哦,谢谢。相关引用:“请注意,C++中实现的C回调函数必须是 extern "C"。尽管类静态函数通常使用与C函数相同的调用约定,它可能被认为是一个静态函数在类中工作。然而,这样做等于在等待错误发生(请参见下面的评论),因此请不要这样做——请改用 extern "C" 包装器。”(有趣的讨论主题) - Magnus Hoff
@MagnusHoff,是的,在Windows上,我相信这种情况发生的可能性微乎其微(这也许就是为什么我很长时间都不知道它存在),但最好有更正确的代码。 - chris

0

是的,正如@chris所说,这里有一个隐藏的指针,它将连接参数的末尾。当线程执行时,它不知道要与最后一个参数位置上的指针匹配,然后在完成时无法恢复此函数的堆栈,因此除了全局函数或类的静态成员函数之外,禁止使用非静态成员函数来驱动线程函数。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接