非静态类成员函数作为回调函数

问题描述

当非静态类成员函数作为回调函数时,如果不做任何处理会报错。如下面的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <iostream>
#include <functional>
#include <vector>

class Test{
public:
Test(){}

void Print(int i)
{
std::cout << i << std::endl;
}

void SetValue(int i)
{
v = i;
}

void PrintValue()
{
std::cout << v << std::endl;
}

private:
int v = 0;
};

void callback(void(*f)(int))
{
f(1);
}

int main()
{
Test test;
test.PrintValue();
callback(&Test::Print);
return 0;
}

编译时会报错:

1
error: cannot convert 'void (Test::*)(int)' to 'void (*)(int)'

如果这个函数不使用其他类成员变量和非静态成员函数,那么可以把它声明为静态。这样调用是没问题的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <iostream>
#include <functional>
#include <vector>

class Test{
public:
Test(){}

static void Print(int i)
{
std::cout << i << std::endl;
}

void SetValue(int i)
{
v = i;
}

void PrintValue()
{
std::cout << v << std::endl;
}

private:
int v = 0;
};

void callback(void(*f)(int))
{
f(1);
}

int main()
{
Test test;
test.PrintValue();
callback(&Test::Print);
return 0;
}

但很多时候我们都需要使用类成员变量,例如类中的另一个SetValue函数,必须保留为非静态函数。

此时问题根源在于非静态成员函数调用时有一个隐藏参数this, 表示它与一个类实例相对应。我们在调用时必须把类实例的地址也传给callback函数。

解决方法一:将其硬设成静态函数

设成静态函数后我们无法直接使用类内非静态成员变量,但如果我们把类地址传给这个静态函数,就可以读取,改变这个类的成员变量。因此我们需要给setValue函数加上Test *类型的变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <iostream>
#include <functional>
#include <vector>

class Test{
public:
Test(){}

static void Print(int i)
{
std::cout << i << std::endl;
}

static void SetValue(Test *test, int i)
{
test->v = i;
}

void PrintValue()
{
std::cout << v << std::endl;
}

private:
int v = 0;
};

void callback(void(*f)(Test *, int), Test *test)
{
f(test, 1);
}

int main()
{
Test test;
test.PrintValue();
callback(&Test::SetValue, &test);
test.PrintValue();
return 0;
}

这样我们实现了用类内静态函数调用类内非静态成员变量的功能。

解决方法二:bind

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <iostream>
#include <functional>
#include <vector>

class Test{
public:
Test(){}

static void Print(int i)
{
std::cout << i << std::endl;
}

void SetValue(int i)
{
v = i;
}

void PrintValue()
{
std::cout << v << std::endl;
}

private:
int v = 0;
};

int main()
{
Test test;
test.PrintValue();
auto _callback = std::bind(&Test::SetValue, &test, std::placeholders::_1);
_callback(1);
test.PrintValue();
return 0;
}

thread可以直接用(&A::f, &a, params)的形式

如下面的例子:当thread线程的回调函数是非静态类成员函数时,由于print函数有隐藏参数this,而thread可以直接传入各个参数,这样就能够正确调用实例a的函数print

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include<iostream>
#include<vector>
#include<algorithm>
#include<thread>

using namespace std;

class A{
public:
A(int num){num_ = num};

void print(int a)
{
cout << num_ + a << endl;
}

private:
int num_ = 0;
};

int main()
{
A a(1);
thread t(&A::print, &a, 1);
t.join();
return 0;
}

当这个函数调用发生在类内时,只需要用this取代&a

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include<iostream>
#include<vector>
#include<algorithm>
#include<thread>

using namespace std;

// static void *callback(void *arg, int a);

class A{
public:
A(int num){num_ = num};

void callback()
{
thread t(&A::print, this, 1);
t.join();
}

void print(int a)
{
cout << num_ + a << endl;
}

private:
int num_ = 0;
};

int main()
{
A a(1);
a.callback();
return 0;
}

解决方法三:设置中转函数

如果需要回调函数的接口不方便自由修改,只能传入一个函数指针,比如是某个编写好的库,那我们可以加一个中转函数,此时需要test实例为全局变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <iostream>
#include <functional>
#include <vector>

class Test{
public:
Test(){}

static void Print(int i)
{
std::cout << i << std::endl;
}

void SetValue(int i)
{
v = i;
}

void PrintValue()
{
std::cout << v << std::endl;
}

private:
int v = 0;
};

void callback(void(*f)(int))
{
f(1);
}

Test test;
static void newSetValue(int a)
{
test.SetValue(a);
}

int main()
{
test.PrintValue();
callback(newSetValue);
test.PrintValue();
return 0;
}

非静态类成员函数作为回调函数
https://sisyphus-99.github.io/2023/09/14/类成员函数作为回调函数/
Author
sisyphus
Posted on
September 14, 2023
Licensed under