MFC中类的非静态函数作为工作者线程函数的方法

C++基础 专栏收录该内容
201 篇文章 16 订阅

对于Windows来说,所有的线程都是一样的。但是MFC却把线程区分为两种类型:用户界面线程(User Interface Thread)和工作者线程(Worker Thread)。用户界面线程可以创建窗口并处理发送给这些窗口的消息。工作者线程执行后台任务,因其不接收用户的直接输入,所以不需要窗口和消息循环。

0 工作者线程函数的要求

通过CreateThread()函数可以创建工作者线程。该函数的第三个参数是工作者线程对应的函数。但是对于工作者线程对应的函数必须有如下要求。

0.1 线程函数格式

线程函数格式必须是

DWORD WINAPI ThreadProc(

LPVOID lpParameter

);

其中,函数参数lpParameter的类型是LPVOID,可以指向任意类型的数据。

0.2 线程函数必须是全局的

线程函数必须是全局的或者是类的静态函数。类的工作者线程中一般都要用到该类的成员变量和成员函数,因为线程函数要求是类的静态函数,则在该函数中用到类的成员变量和成员函数都要是静态的。这样定义该类中会含有大量的静态成员。解决该问题的方式将将该类的指针作为线程函数的参数传入到函数中,通过该指针来调用类的成员变量和成员函数。但是该方法又会导致类中包含大量的访问权限为public的成员,破坏了类的封装性。

如果类的非静态函数能够作为工作者线程函数,这样就避免了上述问题。本文将通过类模板和函数模板实现该功能。

1 类模板和函数模板

一个模板(template)就是一个创建类或函数的蓝图或公式。通过该模板,可以创建多个针对不同类型的函数版本或类版本。

1.1定义方式

模板定义以关键字template开始,后面跟着一个模板参数列表(template parameter list),这是一个逗号分隔的一个或多个模板参数的列表(template parameter)的列表,用小于号和大于号包围起来。

1.2 使用方法

在模板名后面的尖括号中提供一些额外信息来指定模板到底实例化成什么样的函数或类。例如,有如下定义的模板函数

template<typename T>
int compare(const T& v1, const T& v2)
{
if (v1 > v2) return 1;
if (v1 < v2) return -1;
return 0;
}
在调用该模板时,使用如下代码

int result = compare<int>(1, 2);
以上代码的含义是将模板函数实例化为类型参数为int 的函数,即

int compare(const int& v1, const int& v2)
{
if (v1 > v2) return 1;
if (v1 < v2) return -1;
return 0;
}
类模板的使用方法与函数模板相同。

2 具体实现

通过两个类模板和一个函数模板将非静态函数作为线程函数使用。其中,两个类模板分别是绑定类模板和线程类模板,函数模板是创建线程模板,如图1所示。

 

1 调用非静态成员函数流程
2.1 绑定类模板的实现

绑定类模板的主要作用是将类和其非静态函数进行绑定。

template<typename xClass, typename xReturn>
class xBind1
{
typedef xReturn(xClass::* CallFunc)();
CallFunc m_CallFunc;
xClass* m_pThis;
public:
xBind1(xClass* pThis, CallFunc func)
{
m_CallFunc = func;
m_pThis = pThis;
}
xReturn Run()
{
return ((*m_pThis).*(m_CallFunc))();
}
};

其中,模板参数列表中的xClass 表示指定的类; xReturn 表示类的非静态成员函数的返回值;在 xBind1 这个模板类中,通过 typedef 定义了函数指针

typedef xReturn(xClass::* CallFunc)();
本行代码的含义是,将CallFunc 类型定义为 xClass 类的成员函数的指针,该成员函数的返回值是 xReturn ,参数是 void

之后,定义了模板类的两个成员变量,m_CallFuncm_pThis,分别表示类的非静态成员函数的指针和类实例的指针;在模板类的构造函数中分别对这两个变量进行赋值;而该类的成员函数Run()则运行类的某个实例的成员函数。

2.2 线程类模板的实现

线程类模板的主要作用是定义一个static成员函数,该成员函数作为CreateThread()函数创建的工作者线程函数,在该函数中调用绑定类xBindRun()函数。

template<typename xClass, typename xReturn>
class xThread1
{
public:
static DWORD WINAPI Thread(LPVOID lp)
{
typedef xBind1<xClass, xReturn> bind;
bind* pThis = (bind*)lp;
pThis->Run();
delete pThis;
return 0;
}
};
其中,Thread() 函数是该类的静态成员函数,其格式与“ 0.1 线程函数格式”中提到的函数格式相同。 Thread() 函数的参数是 xBind1 类的指针,通过该指针调用 xBind1 类的 Run()

2.3 创建线程函数模板的实现

创建线程函数模板的作用是通过CreateThread()函数创建线程。

template<typename xClass, typename xReturn>
inline HANDLE StartThread(xClass* pThis, xReturn(xClass::* pfn)())
{
xBind1<xClass, xReturn>* pbin = new xBind1<xClass, xReturn>(pThis, pfn);
return CreateThread(0, 0, xThread1<xClass, xReturn>::Thread, pbin, 0, 0);
}
其中,inline 表示该函数为内联函数;该函数的两个参数分别表示类的指针和类非静态成员函数的指针;在该函数中,首先通过 new 关键词新建 xBind1 类的对象,之后通过 CreateThread() 函数创建工作者线程,该线程的函数是 xThread1 类的静态函数 Thread ,其参数是 xBind1 类的对象。

这样,通过线程函数模板StartThread()就创建了新线程,在该线程中调用xThread1Thread()函数,最终调用了指定类的某个实例的成员函数,即类的非静态成员函数。


以下为完整代码,在名为“ThreadTemplate.h”中的文件中有如下代码

#pragma once
namespace ThreadTemplate
{
	template<typename xClass, typename xReturn>
	class xBind1
	{
		typedef xReturn(xClass::* CallFunc)();
		CallFunc m_CallFunc;
		xClass* m_pThis;
	public:
		xBind1(xClass* pThis, CallFunc func)
		{
			m_CallFunc = func;
			m_pThis = pThis;
		}

		xReturn Run()
		{
			return ((*m_pThis).*(m_CallFunc))();
		}
	};

	template<typename xClass, typename xReturn>
	class xThread1
	{
	public:
		static DWORD WINAPI Thread(LPVOID lp)
		{
			typedef xBind1<xClass, xReturn> bind;
			bind* pThis = (bind*)lp;
			pThis->Run();
			delete pThis;
			return 0;
		}
	};

	template<typename xClass, typename xReturn>
	inline HANDLE StartThread(xClass* pThis, xReturn(xClass::* pfn)())
	{
		xBind1<xClass, xReturn>* pbin = new xBind1<xClass, xReturn>(pThis, pfn);
		return CreateThread(0, 0, xThread1<xClass, xReturn>::Thread, pbin, 0, 0);
	}
}
在CNB_Socket_ClientDlg.cpp源文件中包含该头文件,并且创建CNB_Socket_ClientDlg类的非静态成员函数ThreadTest()的线程

DWORD CNB_Socket_ClientDlg::ThreadTest()
{
	while (1)
	{
		m_num_test++;
		Sleep(2000);
	}
	return 0;
}


ThreadTemplate::StartThread<CNB_Socket_ClientDlg, DWORD>(this, &CNB_Socket_ClientDlg::ThreadTest);


  • 0
    点赞
  • 0
    评论
  • 1
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值