如何在需要自由函数的地方传递成员函数

How can I pass a member function where a free function is expected?

本文关键字:函数 方传递 成员 自由      更新时间:2023-10-16

问题如下:考虑这段代码:

#include <iostream>

class aClass
{
public:
void aTest(int a, int b)
{
printf("%d + %d = %d", a, b, a + b);
}
};
void function1(void (*function)(int, int))
{
function(1, 1);
}
void test(int a,int b)
{
printf("%d - %d = %d", a , b , a - b);
}
int main()
{
aClass a;
function1(&test);
function1(&aClass::aTest); // <-- How should I point to a's aClass::test function?
}

如何使用aaClass::test作为function1的参数?我想访问该类的成员。

使用函数指针没有任何问题。但是,指向非静态成员函数的指针与普通函数指针不同:需要在作为隐式参数传递给函数的对象上调用成员函数。上面您的成员函数的签名是,因此

void (aClass::*)(int, int)

而不是您尝试使用的类型

void (*)(int, int)

一种方法可能是使成员函数static在这种情况下,它不需要调用任何对象,您可以将其与类型void (*)(int, int)一起使用。

如果您需要访问类的任何非静态成员,并且需要坚持使用函数指针,例如,因为该函数是 C 接口的一部分,最好的选择是始终将void*传递给采用函数指针的函数,并通过转发函数调用您的成员,该转发函数从void*获取对象,然后调用成员函数。

在适当的C++接口中,您可能希望查看如何让函数为函数对象采用模板化参数以使用任意类类型。如果不希望使用模板化接口,则应使用类似std::function<void(int, int)>的内容:您可以为这些创建适当可调用的函数对象,例如,使用std::bind()

使用类类型的模板参数或合适的std::function<...>的类型安全方法比使用void*接口更可取,因为它们消除了由于强制转换为错误类型而导致错误的可能性。

为了阐明如何使用函数指针调用成员函数,下面是一个示例:

// the function using the function pointers:
void somefunction(void (*fptr)(void*, int, int), void* context) {
fptr(context, 17, 42);
}
void non_member(void*, int i0, int i1) {
std::cout << "I don't need any context! i0=" << i0 << " i1=" << i1 << "n";
}
struct foo {
void member(int i0, int i1) {
std::cout << "member function: this=" << this << " i0=" << i0 << " i1=" << i1 << "n";
}
};
void forwarder(void* context, int i0, int i1) {
static_cast<foo*>(context)->member(i0, i1);
}
int main() {
somefunction(&non_member, nullptr);
foo object;
somefunction(&forwarder, &object);
}

@Pete Becker 的回答很好,但您也可以在不将class实例作为显式参数传递给 C++ 11 中的function1的情况下执行此操作:

#include <functional>
using namespace std::placeholders;
void function1(std::function<void(int, int)> fun)
{
fun(1, 1);
}
int main (int argc, const char * argv[])
{
...
aClass a;
auto fp = std::bind(&aClass::test, a, _1, _2);
function1(fp);
return 0;
}

指向成员函数的指针不同于指向函数的指针。为了通过指针使用成员函数,你需要一个指向它的指针(显然)和一个对象来应用它。所以function1的适当版本将是

void function1(void (aClass::*function)(int, int), aClass& a) {
(a.*function)(1, 1);
}

并称之为:

aClass a; // note: no parentheses; with parentheses it's a function declaration
function1(&aClass::test, a);

自 2011 年以来,如果您可以更改function1,请这样做,如下所示:

#include <functional>
#include <cstdio>
using namespace std;
class aClass
{
public:
void aTest(int a, int b)
{
printf("%d + %d = %d", a, b, a + b);
}
};
template <typename Callable>
void function1(Callable f)
{
f(1, 1);
}
void test(int a,int b)
{
printf("%d - %d = %d", a , b , a - b);
}
int main()
{
aClass obj;
// Free function
function1(&test);
// Bound member function
using namespace std::placeholders;
function1(std::bind(&aClass::aTest, obj, _1, _2));
// Lambda
function1([&](int a, int b) {
obj.aTest(a, b);
});
}

(现场演示)

另请注意,我修复了损坏的对象定义(aClass a();声明了一个函数)。

我问了一个类似的问题(C++开放框架从其他类传递void),但我找到的答案更清楚,所以这里是对未来记录的解释:

使用 std::function 更容易,如下所示:

void draw(int grid, std::function<void()> element)

然后调用为:

grid.draw(12, std::bind(&BarrettaClass::draw, a, std::placeholders::_1));

甚至更容易:

grid.draw(12, [&]{a.draw()});

在其中创建一个 lambda,该 lambda 调用通过引用捕获它的对象

重要的是要注意,除非你可以更改接受函数的代码的签名,否则没有(简单的)方法来做到这一点。这将尝试使用没有与函数相同的闭包的语言实现闭包(C++ 中闭包的签名不同)。

有两种实际方法可以实现此目的:

  1. 使用存储闭包的某种单例/全局变量,然后传递使用该闭包调用所需函数的帮助程序函数。下面是一个示例:
#include <stdio.h>
template<class C, typename ReturnType, typename... Args>
class ClosureSingleton {
typedef ReturnType (C::*FuncType)(Args...);
public:
static ClosureSingleton& getInstance() {
static ClosureSingleton instance;
return instance;
}
void setClosure(C* obj, FuncType f) {
this->obj      = obj;
this->function = f;
}
static ReturnType funcPtr(Args... args) {
C* obj    = getInstance().obj;
auto func = getInstance().function;
return (obj->*func)(args...);
}
private:
ClosureSingleton() {}
C* obj;
FuncType function;
public:
ClosureSingleton(ClosureSingleton const&) = delete;
void operator=(ClosureSingleton const&)   = delete;
};
class aClass {
public:
void aTest1(int a, int b) { printf("%d + %d = %dn", a, b, a + b); }
int aTest2(int a, int b) { return a + b; }
};
void function1(void (*function)(int, int)) {
function(1, 1);
}
int function2(int (*function)(int, int)) {
return function(1, 1);
}
int main() {
aClass tmp;
ClosureSingleton<aClass, void, int, int>::getInstance().setClosure(
&tmp, &aClass::aTest1);
function1(&ClosureSingleton<aClass, void, int, int>::funcPtr);
ClosureSingleton<aClass, int, int, int>::getInstance().setClosure(
&tmp, &aClass::aTest2);
printf(
"function2: %dn",
function2(&ClosureSingleton<aClass, int, int, int>::funcPtr));
return 0;
}

当然,这有一个明显的缺点,即需要在每次调用之前设置闭包,以及一些线程安全问题。不理想,但在特定情况下可能可行

  1. 使用 asmjit 或动态编译之类的东西来动态编译并将函数传递给 C 代码。这仅适用于允许将堆部分标记为可执行文件的计算机。它也是非常不可移植的,因为您将编写汇编代码来完成此操作。但是,如果你让它工作,你确实会有一个真正的闭包,尽管与大多数编程语言实现闭包的方式相比,创建闭包的成本要高得多(它们不复制函数程序集,而是使用上下文对象)
  2. 修补具有函数处理程序的 lib/dll,以更改其签名以允许上下文对象。同样,一个非常脆弱且非最佳解决方案。

我最初的答案,并没有真正回答这个问题,但人们发现它很有用:

不知道为什么这个令人难以置信的简单解决方案被放弃了:

#include <stdio.h>
class aClass
{
public:
void aTest(int a, int b)
{
printf("%d + %d = %dn", a, b, a + b);
}
};
template<class C>
void function1(void (C::*function)(int, int), C& c)
{
(c.*function)(1, 1);
}
void function1(void (*function)(int, int)) {
function(1, 1);
}
void test(int a,int b)
{
printf("%d - %d = %dn", a , b , a - b);
}
int main (int argc, const char* argv[])
{
aClass a;
function1(&test);
function1<aClass>(&aClass::aTest, a);
return 0;
}

输出:

1 - 1 = 0
1 + 1 = 2

我使成员函数为静态并且所有工作:

#include <iostream>
class aClass
{
public:
static void aTest(int a, int b)
{
printf("%d + %d = %dn", a, b, a + b);
}
};
void function1(int a,int b,void function(int, int))
{
function(a, b);
}
void test(int a,int b)
{
printf("%d - %d = %dn", a , b , a - b);
}
int main (int argc, const char* argv[])
{
aClass a;
function1(10,12,test);
function1(10,12,a.aTest); // <-- How should I point to a's aClass::test function?
getchar();return 0;
}

如果您实际上不需要使用该实例a(即你可以像@mathengineer的答案一样让它静态) 您可以简单地传入非捕获 lambda。(衰减到函数指针)

<小时 />
#include <iostream>
class aClass
{
public:
void aTest(int a, int b)
{
printf("%d + %d = %d", a, b, a + b);
}
};
void function1(void (*function)(int, int))
{
function(1, 1);
}
int main()
{
//note: you don't need the `+`
function1(+[](int a,int b){return aClass{}.aTest(a,b);}); 
}

魔杖盒


注意:如果aClass的构造成本很高或有副作用,这可能不是一个好方法。

你现在可以停止敲头了。下面是成员函数的包装器,以支持将纯C函数作为参数的现有函数。thread_local指令是这里的关键。

http://cpp.sh/9jhk3

// Example program
#include <iostream>
#include <string>
using namespace std;
typedef int FooCooker_ (int);
// Existing function
extern "C" void cook_10_foo (FooCooker_ FooCooker) {
cout << "Cooking 10 Foo ..." << endl;
cout << "FooCooker:" << endl;
FooCooker (10);
}
struct Bar_ {
Bar_ (int Foo = 0) : Foo (Foo) {};
int cook (int Foo) {
cout << "This Bar got " << this->Foo << endl;
if (this->Foo >= Foo) {
this->Foo -= Foo;
cout << Foo << " cooked" << endl;
return Foo;
} else {
cout << "Can't cook " <<  Foo << endl;
return 0;
}
}
int Foo = 0;
};
// Each Bar_ object and a member function need to define
// their own wrapper with a global thread_local object ptr
// to be called as a plain C function.
thread_local static Bar_* Bar1Ptr = NULL;
static int cook_in_Bar1 (int Foo) {
return Bar1Ptr->cook (Foo);
}
thread_local static Bar_* Bar2Ptr = NULL;
static int cook_in_Bar2 (int Foo) {
return Bar2Ptr->cook (Foo);
}
int main () {
Bar1Ptr = new Bar_ (20);
cook_10_foo (cook_in_Bar1);

Bar2Ptr = new Bar_ (40);
cook_10_foo (cook_in_Bar2);

delete Bar1Ptr;
delete Bar2Ptr;
return 0;
}

请就此方法的任何问题发表评论。

其他答案无法调用现有的C函数:http://cpp.sh/8exun