C++中类成员函数作为回调函数

C++中类成员函数作为回调函数

背景

实现了一个C的组件以后,用在QT中,发现有点问题。为了解决调用成员函数作为回调函数,而又不想改成信号槽。特此学习了别人的做法。

原文(有修改):https://blog.csdn.net/this_capslock/article/details/17001003、http://blog.csdn.net/this_capslock/article/details/38564719

前言

回调函数是基于C编程的Windows SDK的技术,不是针对C++的,程序员可以将一个C函数直接作为回调函数,但是如果试图直接使用C++的成员函数作为回调函数将发生错误,甚至编译就不能通过。

普通的C++成员函数都隐含了一个传递函数作为参数,亦即“this”指针,C++通过传递一个指向自身的指针给其成员函数从而实现程序函数可以访问C++的数据成员。这也可以理解为什么C++类的多个实例可以共享成员函数但是确有不同的数据成员。由于this指针的作用,使得将一个CALLBACK型的成员函数作为回调函数安装时就会因为隐含的this指针使得函数参数个数不匹配,从而导致回调函数安装失败。

这样从理论上讲,C++类的成员函数是不能当作回调函数的。但我们在用C++编程时总希望在类内实现其功能,即要保持封装性,如果把回调函数写作普通函数有诸多不便。经过网上搜索和自己研究,发现了几种巧妙的方法,可以使得类成员函数当作回调函数使用。

这里采用Linux C++中线程创建函数pthread_create举例,其原型如下:

int pthread_create( pthread_t *restrict tidp , const pthread_attr_t *restrict attr , void* (*start_rtn)(void*) , void *restrict arg );

这里我们只关注第三个参数start_run,它是一个函数指针,指向一个以void*为参数,返回值为void*的函数,这个函数被当作线程的回调函数使用,线程启动后便会执行该函数的代码。

做法

方法1

思路:回调函数为普通函数,但在函数体内执行成员函数。

见以下代码:

class MyClass
{
	pthread_t TID;
public:
	void func()
	{
		//子线程执行代码
	}
 
	bool startThread()
	{//启动子线程
		int ret = pthread_create( &TID , NULL , callback , this );
		if( ret != 0 )
			return false;
		else
			return true;
	}
};
 
static void* callback( void* arg )
{//回调函数
	((MyClass*)arg)->func();调用成员函数
	return NULL;
}
 
int main()
{
	MyClass a;
	a.startThread();
}

类MyClass需要在自己内部开辟一个子线程来执行成员函数func()中的代码,子线程通过调用startThread()成员函数来启动。这里将回调函数callback写在了类外面,传递的参数是一个指向MyClass对象的指针(在pthrad_create()中由第4个参数this指定),回调函数经过强制转换把void变为MyClass,然后再调用arg->func()执行子线程的代码。

这样做的原理是把当前对象的指针当作参数先交给一个外部函数,再由外部函数调用类成员函数,以外部函数作为回调函数,但执行的是成员函数的功能,这样相当于在中间作了一层转换。缺点是回调函数在类外,影响了封装性,这里把callback()限定为static,防止在其它文件中调用此函数。

方法2

思路:回调函数为类内静态成员函数,在其内部调用成员函数。

在方法1上稍作更改,把回调函数搬到类MyClass里,这样就保持了封装性。

代码如下:

class MyClass
{
	static MyClass* CurMy;//存储回调函数调用的对象
	static void* callback(void*);//回调函数
	pthread_t TID;
	void func()
	{
		//子线程执行代码
	}
	
	void setCurMy()
	{//设置当前对象为回调函数调用的对象
		CurMy = this;
	}
public:
	bool startThread()
	{//启动子线程
		setCurMy();
		int ret = pthread_create( &TID , NULL , MyClass::callback , NULL );
		if( ret != 0 )
			return false;
		else
			return true;
	}
};
MyClass* MyClass::CurMy = NULL;
void* MyClass::callback(void*)
{
	CurMy->func();
	return NULL;
}
 
int main()
{
	MyClass a;
	a.startThread();
}

类MyClass有了1个静态数据成员CurMy和1个静态成员函数callback。CurMy用来存储一个对象的指针,充当方法一中回调函数的参数arg。callback当作回调函数,执行CurMy->func()的代码。每次建立线程前先要调用setCurMy()来让CurMy指向当前自己。

这个方法的好处时封装性得到了很好的保护,MyClass对外只公开一个接口startThread(),子线程代码和回调函数都被设为私有,外界不可见。另外没有占用callback的参数,可以从外界传递参数进来。但每个对象启动子线程前一定要注意先调用setCurMy()让CurMy正确的指向自身,否则将为其它对象开启线程,这样很引发很严重的后果。

方法3

思路:对成员函数进行强制转换,当作回调函数。

代码如下:


// 关键的定义在这里。
class MyClass;
union for_callback {
    void *(*fun_in_c)(void *);
    void *(MyClass::*fun_in_class)(void);
};

class MyClass
{
    int ret;
    pthread_t TID;

public:
    union for_callback fp;

    void *func(void)
    {
        cout << "fun_in_class" << endl;
        cout << this->ret << endl;
        return NULL;
    }

    bool startThread()
    {
        fp.fun_in_class = &MyClass::func;

        ret = pthread_create(&TID, NULL, fp.fun_in_c, this); // 创建线程
        return true;
    }
};

这个方法是原理是, MyClass::func最终会转化成 void func(MyClass *this); 也就是说在原第一个参数前插入指向对象本身的this指针。

可以利用这个特性写一个非静态类成员方法来直接作为线程回调函数。

对编译器而言,void (MyClass::*FUNC1)(void)void* (*FUNC)(void*)这两种函数指针虽然看上去很不一样,但他们的最终形式是相同的,因此就可以把成员函数指针强制转换成普通函数的指针来当作回调函数。

在建立线程时要把当前对象的指针this当作参数传给回调函数(成员函数func),这样才能知道线程是针对哪个对象建立的。

方法三的封装性比方法二更好,因为不涉及多个对象共用一个静态成员的问题,每个对象可以独立地启动自己的线程而不影响其它对象。

方法4

使用C++的TR1中中包含一个function模板类和bind模板函数。使用它们可以实现类似函数指针的功能,但是比函数指针更加灵活。

对于tr1::function对象可以这么理解:它能接受任何可调用物,只要可调用物的的签名式兼容于需求端即可,比如函数指针,仿函数对象,成员函数指针,

例子如下:

#include <iostream>
#include <functional>//为了使用std::tr1::function
#include <string>
#include <sstream>
using namespace std;

typedef tr1::function< int (const string&) > FUNC;
void InterfaceFunc( const string& a , const string& b , FUNC f )
{//测试用接口函数,将a+b得到的字符串转成整数并打印出来,f是回调函数
    cout << f(a+b) << endl;
}

先自定义了一种function类型FUNC,它接受所有参数为const string&并且返回值是 int的签名对象。

函数InterfaceFunc第三个参数是一个FUNC对象,当作回调函数使用

下面是四种常见类型的“可调用物”:

int f1( const string& str )
{//正常函数
	cout << "int f1( const string& str )" << endl;
	stringstream ss;
	ss << str;
	int result;
	ss >> result;
	return result;
}
 
class F2
{
public:
	int operator()( const string& str )
	{//仿函数
		cout << "int F2::operator()( const string& str )" << endl;
		stringstream ss;
		ss << str;
		int result;
		ss >> result;
		return result;
	}
};
 
class F3
{
public:
	int f3( const string& str )
	{//类内非静态成员函数
		cout << "int F3::f3( const string& str )" << endl;
		stringstream ss;
		ss << str;
		int result;
		ss >> result;
		return result;
	}
};
 
class F4
{
public:
	static int f4( const string& str )
	{//类内静态成员函数
		cout << "static int F4::f4( const string& str )" << endl;
		stringstream ss;
		ss << str;
		int result;
		ss >> result;
		return result;
	}
};

这些函数都具有形如int (const string& )形式的签名式,所以都可以被FUNC对象接受。

具体调用的时候是这样的:

int main()
{
	string a = "123";
	string b = "456";
 
	//FUNC接受正常函数指针
	InterfaceFunc( a , b , f1 );
	cout << endl;
 
	//FUNC接受仿函数
	InterfaceFunc( a , b , F2() );
	cout << endl;
 
	//FUNC接受类内非静态成员函数
	F3 f;
	InterfaceFunc( a , b , tr1::bind( &F3::f3 , &f , tr1::placeholders::_1 ) );
	cout << endl;
 
	//FUNC接受类内静态成员函数
	InterfaceFunc( a , b , F4::f4 );
	system("pause");
}

这里需要特别注意下,第三次让FUNC接受类内非静态成员函数时,使用了tr1::bind( &F3::f3 , &f , tr1::placeholders::_1 )这样东西作为参数。

它的含义是:让&f作为F3::f3函数中的第1个参数,因为对于类内非静态成员函数,它有一个隐含的第一参数:this指针,因为成员函数是存储位置是在对象之外的,只根据F3::f3的地址无法得知具体对哪个对象操作,tr1::bind的作用正是告诉F3::f3,它隐含的this参数指向的是f这个已经定义好的对象,剩下的事情就和其它情况一样了。

如果说我的文章对你有用,只不过是我站在巨人的肩膀上再继续努力罢了。
若在页首无特别声明,本篇文章由 Schips 经过整理后发布。
博客地址:https://www.cnblogs.com/schips/
原文地址:https://www.cnblogs.com/schips/p/using_cpp_class_member_function_as_normal_callback.html