当在类中需要创建线程时,总是因为线程函数需要定义成静态成员函数,但是又需要访问非静态数据成员这种需求,来做若干重复性的繁琐工作。比如我以前就经常定义一个静态成员函数,然后定一个结构体,结构体形式如下所示,将类指针传入到线程函数中以方便访问费非态成员变量。
struct THREAD_PARAMER { CTestClass* pThis; PVOID pContext; }
其实这里不算解决问题吧,应该是用一些其他的方式来减少这种重复性工作。
根据线程函数的要求,除了可以弄成静态成员函数外,其实也可以是全局函数。所以其实不定义静态成员函数也可以在类中创建线程,那重点就是如果把类对象指针、具体执行的函数、需要传递的上下文参数这三个类内部的信息传递到全局的线程函数中呢?
我想到的方法仍然脱离不了封装,因为实际的线程函数只接受一个参数,如果要传递三个过去,必然需要封装出一个新的类型来进行传递。
所以这里要在全局线程中去间接调用类中的成员函数,达到让这个成员函数伪装成线程函数的目的,首先要做两点:
1、封装API函数CreateThread,直接传递类对象指针、成员函数、需要的函数进去就能创建线程,并执行到成员函数中去。
2、对于不同的类,方法要一致,这里就考虑使用模板参数来代替类类型。
有了上面的总结,经过实验写出了如下代码来简化在类中创建线程,首先上测试代码,这部分代码后面不再改变。
#include <iostream> #include "ThreadInClass.h" class CTest { public: void SayHelloInThread(char* nscName) { ThreadInClass::CThreadActuator<CTest>::StartThreadInClass(this, &CTest::ThreadWork, nscName); } DWORD ThreadWork(void* p) { HANDLE lhThread = GetCurrentThread(); if(NULL != lhThread) { DWORD ldwThreadID = GetThreadId(lhThread); std::cout << "子线程ID: " << ldwThreadID << std::endl; CloseHandle(lhThread); } std::cout << "hello, " << (char*)p << std::endl; return 0; } }; void main() { HANDLE lhMainThread = GetCurrentThread(); if(NULL != lhMainThread) { DWORD ldwThreadID = GetThreadId(lhMainThread); std::cout << "主线程ID: " << ldwThreadID << std::endl; CloseHandle(lhMainThread); } CTest loTest; char* lscName = "colin"; loTest.SayHelloInThread(lscName); system("pause"); return; }
下面是封装的类模板,注意我这里简化了StartThreadInClass函数没有列出CreateThread的可用参数,如果需要的话在这里加上即可。
#include <iostream>
#include<functional>
using std::function;
#include<Windows.h>
namespace ThreadInClass{
// 参数容器模板类,用于存放要调用的类对象、函数、及其参数。(返回值不用存放,因为返回值要作为线程结束状态,所以必须为DWORD)
template<typename tClassName>
class CRealThreadParamer
{
private:
typedef std::function<DWORD(tClassName*, PVOID)> RealExcuteFun;
RealExcuteFun mfExcuteFun;
tClassName* mpoInstance;
PVOID mpoContext;
public:
CRealThreadParamer(tClassName* npThis, RealExcuteFun nfWorkFun, PVOID npContext){
mpoInstance= npThis;
mfExcuteFun= nfWorkFun;
mpoContext= npContext;
}
DWORD Run()
{
return mfExcuteFun(mpoInstance, mpoContext);
}
};
// 线程创建执行类,用于提供创建线程和执行线程的接口封装
template<typename tClassName>
class CThreadActuator
{
public:
typedef CRealThreadParamer<tClassName> CThreadParamer;
typedef std::function<DWORD(tClassName*, PVOID)> RealExcuteFun;
static HANDLE StartThreadInClass(tClassName* npThis, RealExcuteFun nfWorkFun, PVOID npContext)
{
CThreadParamer* lpoParamer = new CThreadParamer(npThis, nfWorkFun, npContext);
return CreateThread(nullptr, 0, CThreadActuator::ThreadDispatch, (PVOID)lpoParamer, 0, nullptr);
}
static DWORD WINAPI ThreadDispatch(PVOID npParam)
{
if(nullptr == npParam)
return 0;
else
{
CThreadParamer* lfThreadParamer = (CThreadParamer*)npParam;
DWORD ldwRet= lfThreadParamer->Run();
delete lfThreadParamer;
lfThreadParamer= NULL;
return ldwRet;
}
}
};
}
我这里用到了std::funciton,而这里的用法有点类似于使用typddef的方式去声明一种函数类型。
执行结果如下:
当再次查看这部分代码时,我发现CReadThreadParamer的作用就是一个提供调用形如DWORD (tClassName*, PVOID)函数的接口,并且一旦创建了,它的调用形式也固定了(因为参数都是构造的时候就传递进去了)。
这让我想到了bind,平常使用这个不多,但是知道它可以绑定到一个函数上,并减少或者增加这个函数的参数来调用。既然我这里参数都是固定死了,那是不是可以使用bind先把这些参数全部绑定上去,然后在调用的时候只需调用形如DWORD()的函数就可以了呢?
经过尝试,CReadThreadParamer现在可以优化成这个样子了:
template<typename tClassName> class CRealThreadParamer { private: typedef std::function<DWORD(tClassName*, PVOID)> RealExcuteFun; typedef std::function<DWORD()> NewRealExcuteFun; NewRealExcuteFun mfExcuteFun; public: CRealThreadParamer(tClassName* npThis, RealExcuteFun nfWorkFun, PVOID npContext) { mfExcuteFun = std::tr1::bind(nfWorkFun, npThis, npContext); } DWORD Run() { return mfExcuteFun(); } };
再细细看了下现在的CRealThreadParamer,构造函数里直接把所有的参数绑定到了实际执行的函数上,所以内内部只需要保存一个std::function类型了。
等等,既然只有一个std::function类型了,那我之前增加这个类来保存三个类中的参数还有什么意义,直接传递这么一个类型不就行了吗?
也就是说,应该是可以在StartThreadInClass的实现中就把所有参数绑定成一个函数调用,然后保存到std::function传递给线程函数,线程函数再执行这个函数就行了。
根据上述思路,进一步优化后,代码简化了很多很多了,如下:
#include <iostream> #include <functional> using std::function; #include <Windows.h> namespace ThreadInClass{ // 线程创建执行类,用于提供创建线程和执行线程的接口封装 template<typename tClassName> class CThreadActuator { public: typedef std::function<DWORD(tClassName*, PVOID)> RealExcuteFun; typedef std::function<DWORD()> NewRealExcuteFun; static HANDLE StartThreadInClass(tClassName* npThis, RealExcuteFun nfWorkFun, PVOID npContext) { NewRealExcuteFun* lpoParamer = new NewRealExcuteFun(std::tr1::bind(nfWorkFun, npThis, npContext)); return CreateThread(nullptr, 0, CThreadActuator::ThreadDispatch, (PVOID)lpoParamer, 0, nullptr); } static DWORD WINAPI ThreadDispatch(PVOID npParam) { if(nullptr == npParam) return 0; else { NewRealExcuteFun* lfThreadParamer = (NewRealExcuteFun*)npParam; DWORD ldwRet = (*lfThreadParamer)(); delete lfThreadParamer; lfThreadParamer = NULL; return ldwRet; } } }; }
到了第三版,我再没有想到还可以简化的方式了,不过到是发现了,如果在使用的时候,我需要传入的上下文内容比较多,还是需要自己构造一个结构体来存放上下文信息。因为类中用来做线程函数(间接的)的形式是固定为DWORD(PVOID)类型的。
那么有没有一种方式可以让这个函数可以有任意多个不同类型的参数呢?其实是有的,那就是使用C++11的类可变参模板。
在更改代码之前,先测试一下直接使用tuple类型作为上下文参数传递,因为它可以存放很多不同类型的数据到 一个变量中,从某种程度上也是可以满足多个上下文参数的。
测试代码如下:
#include <iostream> #include "ThreadInClass.h" #include <tuple> using std::tr1::tuple; class CTest { public: void SayHelloInThread(char* nscName) { ThreadInClass::CThreadActuator<CTest>::StartThreadInClass(this, &CTest::DoSayHelloInThread, nscName); } DWORD DoSayHelloInThread(void* p) { HANDLE lhThread = GetCurrentThread(); if(NULL != lhThread) { DWORD ldwThreadID = GetThreadId(lhThread); std::cout << "DoSayHelloInThread线程ID: " << ldwThreadID << std::endl; CloseHandle(lhThread); } std::cout << "hello, " << (char*)p << std::endl; return 0; } void PrintSumInThread(tuple<int, int>& roAddTupleInfo) { ThreadInClass::CThreadActuator<CTest>::StartThreadInClass(this, &CTest::DoPrintSumInThread, &roAddTupleInfo); } DWORD DoPrintSumInThread(void* p) { HANDLE lhThread = GetCurrentThread(); if(NULL != lhThread) { DWORD ldwThreadID = GetThreadId(lhThread); std::cout << "DoPrintSumInThread线程ID: " << ldwThreadID << std::endl; CloseHandle(lhThread); } std::tr1::tuple<int, int>* lpoT1 = (std::tr1::tuple<int, int>*)p; int i = std::tr1::get<0>(*lpoT1); int j = std::tr1::get<1>((*lpoT1)); std::cout << i << " + " << j << " = " << i + j << std::endl; return 0; } }; void main() { HANDLE lhMainThread = GetCurrentThread(); if(NULL != lhMainThread) { DWORD ldwThreadID = GetThreadId(lhMainThread); std::cout << "主线程ID: " << ldwThreadID << std::endl; CloseHandle(lhMainThread); } CTest loTest; char* lscName = "colin"; loTest.SayHelloInThread(lscName); tuple<int, int> t1(1, 2); loTest.PrintSumInThread(t1); system("pause"); return; }运行结果:
经实验是可以的。不过相对于使用变参模板而言,这种方式需要使用者自己定义出一个tuple,来存放所有要传递的数据,还是不如直接传递来的直观。
接下来更改代码使用变参模板。这里囧了,变参模板在我的VS2010里编译不过,提示错误为:error C2143: syntax error : missing ‘,‘ before ‘...‘
似乎2010的编译器还没有支持这个?那为什么tuple可以正常使用呢?不明所以啊。。
不过代码我倒是改好了,就是编译不过,这里先记录下来,后面等装了2012再测试下吧。
// 线程创建执行类,用于提供创建线程和执行线程的接口封装 //template<typename tClassName> template<typename tClassName, typename... ArgsType> class CThreadActuator { public: //typedef std::function<DWORD(tClassName*, PVOID)> RealExcuteFun; typedef std::function<DWORD(tClassName*, ArgsType...)> RealExcuteFun; typedef std::function<DWORD()> NewRealExcuteFun; //static HANDLE StartThreadInClass(tClassName* npThis, RealExcuteFun nfWorkFun, PVOID npContext) //{ // NewRealExcuteFun* lpoParamer = new NewRealExcuteFun(std::tr1::bind(nfWorkFun, npThis, npContext)); // return CreateThread(nullptr, 0, CThreadActuator::ThreadDispatch, (PVOID)lpoParamer, 0, nullptr); //} static HANDLE StartThreadInClass(tClassName* npThis, RealExcuteFun nfWorkFun, ArgsType... npArgs) { NewRealExcuteFun* lpoParamer = new NewRealExcuteFun(std::tr1::bind(nfWorkFun, npThis, npArgs...)); return CreateThread(nullptr, 0, CThreadActuator::ThreadDispatch, (PVOID)lpoParamer, 0, nullptr); } static DWORD WINAPI ThreadDispatch(PVOID npParam) { if(nullptr == npParam) return 0; else { NewRealExcuteFun* lfThreadParamer = (NewRealExcuteFun*)npParam; DWORD ldwRet = (*lfThreadParamer)(); delete lfThreadParamer; lfThreadParamer = NULL; return ldwRet; } } };
补充说明一下,因为编译不能通过这个,所以还尝试了使用变长参数,在模板中解析tuple当方式来解决类成员函数参数固定这个不好的地方,但是发现都解决不了。看来最好的方式就是变长参数模板了,等有环境了一定要再试试。
通过优化这么个工具,一方面熟悉了一下C++模板技术的使用,另一方面增加了我对C++11的function、bind、tuple以及模板变长参数的理解和使用。
总结一下现在的方便之处和不方便的地方。
好处:
相对于之前直接在类中定义静态成员函数的方式去创建线程,使用这个模板更加间接了。
缺陷:
1、还不能最直观的传递无限制数目和类型的参数到类成员函数中。(我觉得变长参数模板应该能解决这个问题的)
2、因为使用了较多的bind和function,如果有发生错误会比较难懂(对于我来说哈)。
原文:http://www.cnblogs.com/monotone/p/4366170.html