利用C++11的function和bind简化类建立线程

问题引出

当在类中须要建立线程时,老是由于线程函数须要定义成静态成员函数,可是又须要访问非静态数据成员这种需求,来作若干重复性的繁琐工做。好比我之前就常常定义一个静态成员函数,而后定一个结构体,结构体形式以下所示,将类指针传入到线程函数中以方便访问费非态成员变量。ios

struct THREAD_PARAMER
{
    CTestClass*  pThis;
    PVOID pContext;
}

解决问题

其实这里不算解决问题吧,应该是用一些其余的方式来减小这种重复性工做。c++

根据线程函数的要求,除了能够弄成静态成员函数外,其实也能够是全局函数。因此其实不定义静态成员函数也能够在类中建立线程,那重点就是如何把类对象指针、具体执行的函数、须要传递的上下文参数这三个类内部的信息传递到全局的线程函数中呢?git

我想到的方法仍然脱离不了封装,由于实际的线程函数只接受一个参数,若是要传递三个过去,必然须要封装出一个新的类型来进行传递。github

 

因此这里要在全局线程中去间接调用类中的成员函数,达到让这个成员函数假装成线程函数的目的,首先要作两点:函数

一、封装API函数CreateThread,直接传递类对象指针、成员函数、上下文参数进去就能建立线程,并执行到成员函数中去。测试

二、对于不一样的类,方法要一致,这里就考虑使用模板参数来代替类类型。优化

有必要在这里先声明一下,下面的内容都是我本身根据当时知识程度一步一步深刻的过程,因此若是要找最好的解决方案,能够直接看最后的版本,或者直接去个人github上迁移代码(确定是我目前最新的)。this

初版:使用模板类做为容器保存参数(VS2010)

有了上面的总结,通过实验写出了以下代码来简化在类中建立线程,首先上测试代码,这部分代码后面再也不改变。spa

#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的方式去声明一种函数类型。

执行结果以下:

image

第二版:使用bind将参数绑定到一个function上(VS2010)

当再次查看这部分代码时,我发现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();
        }
    };

 

第三版:传递function类型指针做为参数给线程函数(VS2010)

再细细看了下如今的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;
            }
        }
    };
}

 

第四版:使用变长模板参数解决参数类型单一的缺陷(VS2013)

到了第三版,我再没有想到还能够简化的方式了,不过到是发现了,若是在使用的时候,我须要传入的上下文内容比较多,仍是须要本身构造一个结构体来存放上下文信息。由于类中用来作线程函数(间接的)的形式是固定为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;
}

运行结果:

image

经实验是能够的。不过相对于使用变参模板而言,这种方式须要使用者本身定义出一个tuple,来存放全部要传递的数据,仍是不如直接传递来的直观。

接下来更改代码使用变参模板。

注意:截止到上面测试使用tuple,我一直使用的是VS2010版本。可是当我使用变长参数模板时,发现编译不过去,看错误提示彷佛是还不支持,因此下面我更换到了VS2013,可是VS2013上要将类成员函数赋值给std::function类型时,必须使用bind才行,因此传递参数时要注意。

#include <iostream>
#include <functional>
using std::tr1::function;
using std::tr1::bind;

#include <Windows.h>

namespace ThreadInClass{
    template<typename tClassName, typename... ArgsType>    // 变参模板
    class CThreadActuator
    {
    public:
        typedef function<DWORD()> NewRealExcuteFun;

        /// 使用变参模板
        static HANDLE StartThreadInClass(tClassName* npThis, function<DWORD(tClassName*, ArgsType...)> nfWorkFun, ArgsType... npArgs)
        {        
            NewRealExcuteFun* lpoParamer = new NewRealExcuteFun(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;
            }
        }
    };
}

附上测试代码:

#include <iostream>
using std::cout;
using std::endl;

#include "ThreadInClass.h"

#include <tuple>
using std::tr1::tuple;
using std::tr1::get;

class CTest
{
public:
    void SayHelloInThread(char* nscName)
    {
        ThreadInClass::CThreadActuator<CTest, char* >::StartThreadInClass(this, bind(&CTest::DoSayHelloInThread,
            std::tr1::placeholders::_1, std::tr1::placeholders::_2), nscName);
    }
    DWORD DoSayHelloInThread(void* p)
    {
        HANDLE lhThread = GetCurrentThread();
        if(NULL != lhThread)
        {
            DWORD ldwThreadID = GetThreadId(lhThread);
            cout << "DoSayHelloInThread线程ID: " << ldwThreadID << endl;
            CloseHandle(lhThread);
        }

        cout << "hello, " << (char*)p << endl;
        return 0;
    }
    void PrintSumInThread(tuple<int, int>& roAddTupleInfo)
    {
        ThreadInClass::CThreadActuator<CTest, tuple<int, int>& >::StartThreadInClass(this, bind(&CTest::DoPrintSumInThread,
            std::tr1::placeholders::_1, std::tr1::placeholders::_2), roAddTupleInfo);
    }

    DWORD DoPrintSumInThread(tuple<int, int>& roAddTupleInfo)
    {
        HANDLE lhThread = GetCurrentThread();
        if(NULL != lhThread)
        {
            DWORD ldwThreadID = GetThreadId(lhThread);
            cout << "DoPrintSumInThread线程ID: " << ldwThreadID << endl;
            CloseHandle(lhThread);
        }
        int i = get<0>(roAddTupleInfo);
        int j = get<1>(roAddTupleInfo);

        cout << i << " + " << j << " = " << i + j << endl;
        return 0;
    }

    void PrintSumInThread2(int &i, int &j)
    {
        ThreadInClass::CThreadActuator<CTest, int, int >::StartThreadInClass(this, bind(&CTest::DoPrintSumInThread2, 
            std::tr1::placeholders::_1, std::tr1::placeholders::_2, std::tr1::placeholders::_3), i, j);
    }

    DWORD DoPrintSumInThread2(int i, int j)
    {
        HANDLE lhThread = GetCurrentThread();
        if (NULL != lhThread)
        {
            DWORD ldwThreadID = GetThreadId(lhThread);
            std::cout << "DoPrintSumInThread2线程ID: " << ldwThreadID << std::endl;
            CloseHandle(lhThread);
        }
        std::cout << i << " + " << j << " = " << i + j << std::endl;
        return 0;
    }


};

void main()
{
    HANDLE lhMainThread = GetCurrentThread();
    if(NULL != lhMainThread)
    {
        DWORD ldwThreadID = GetThreadId(lhMainThread);
        cout << "主线程ID: " << ldwThreadID << endl;
        CloseHandle(lhMainThread);
    }

    CTest loTest;
    char* lscName = "colin";
    loTest.SayHelloInThread(lscName);

    tuple<int, int> t1(1, 2);
    loTest.PrintSumInThread(t1);

    int i = 4;
    int j = 5;
    loTest.PrintSumInThread2(i, j);

    system("pause");
    return;
}

执行结果以下:

image

第五版:直接传递绑定好全部参数的function(VS2013)

上面有提到,VS2013要将类成员函数赋值给function类型,必须使用bind。

因此实际调用的时候传递参数时,是将成员函数经过bind(&CTest::DoPrintSumInThread2, std::tr1::placeholders::_1, std::tr1::placeholders::_2, std::tr1::placeholders::_3), 这么传递的。其中参数占位符根据成员函数实际的参数个数来定。而参数在这里建立线程的时候也是固定了的,既然如此,我干吗还用占位符呢?直接传递bind(&CTest::DoPrintSumInThread2, this, i, j)不就好了吗?

经测试上述方案是可行的,仔细想了下,在CreateThread的时候咱们须要传递一个参数给线程函数,这个参数类型咱们定义成了function类型,而上面这种方式其实也是function啊,而且对于任何已经知道要传递的参数值的成员函数,均可以经过bind,返回function< DWORD() >类型。这就意味着在线程函数里,我根本不须要知道其余信息,只须要执行这个function表明的函数就能够了啊。

茅塞顿开,有了以下代码:

#include <iostream>
#include <functional>
using std::tr1::function;
using std::tr1::bind;

#include <Windows.h>
namespace ThreadInClass{
    class CThreadActuator
    {
    public:
        typedef function<DWORD()> NewRealExcuteFun;

        // 使用变参模板
        static HANDLE StartThreadInClass(function<DWORD()> nfWorkFun)
        {        
            NewRealExcuteFun* lpoParamer = new NewRealExcuteFun(nfWorkFun);
            return CreateThread(nullptr, 0, CThreadActuator::ThreadDispatch, (PVOID)lpoParamer, 0, nullptr);
        }
        
        // 真正的线程函数,间接调用类成员函数
        static DWORD WINAPI ThreadDispatch(PVOID npParam)
        {
            if (nullptr == npParam)
                return 0;
            else
            {
                NewRealExcuteFun* lpfWorkFun = (NewRealExcuteFun*)npParam;
                DWORD ldwRet = (*lpfWorkFun)();
                delete lpfWorkFun;
                lpfWorkFun = NULL;
                return ldwRet;
            }
        }
    };
}
#include <iostream>
using std::cout;
using std::endl;
#include "ThreadInClass.h"

class CTest
{
public:
void PrintSumInThread(int &i, int &j)
    {
        ThreadInClass::CThreadActuator::StartThreadInClass(bind(&CTest::DoPrintSumInThread, this, i, j));
    }

    DWORD DoPrintSumInThread(int i, int j)
    {
        HANDLE lhThread = GetCurrentThread();
        if (NULL != lhThread)
        {
            DWORD ldwThreadID = GetThreadId(lhThread);
            std::cout << "DoPrintSumInThread2线程ID: " << ldwThreadID << std::endl;
            CloseHandle(lhThread);
        }
        std::cout << i << " + " << j << " = " << i + j << std::endl;
        return 0;
    }


};

void main()
{
    HANDLE lhMainThread = GetCurrentThread();
    if(NULL != lhMainThread)
    {
        DWORD ldwThreadID = GetThreadId(lhMainThread);
        cout << "主线程ID: " << ldwThreadID << endl;
        CloseHandle(lhMainThread);
    }

    CTest loTest;
    int i = 4;
    int j = 5;
    loTest.PrintSumInThread(i, j);

    system("pause");
    return;
}

呀,不只去掉了模板,代码也简洁了好多倍。

第六版

是的,没错还有第6版本。那就是使用c++11的std::thread,使用方式就很少说了,我也是看的别人的介绍。跟我前面介绍的方式差很少,不过额外增长了不少功能就是了。不过也不是说我前面写的都没用,很大程度上thread内部用的方式其实就是差很少的。

而后对比下个人第四版,第四版中是将类类型和变参参数做为类的模板了。而实际上我那个类里面除了StartThreadInClass中使用了这两个模板参数,其余地方都没有使用。因此实际上是能够直接定义成这个函数的模板参数的,而后配合第五版的直接绑定全部参数的方式:

#include <iostream>
#include <functional>
using std::tr1::function;
using std::tr1::bind;

#include <Windows.h>
namespace ThreadInClass{
    class CThreadActuator
    {
    public:
        typedef function<DWORD()> NewRealExcuteFun;

        // 使用变参模板
        template <typename _Fn, typename... _Args>
        static HANDLE StartThreadInClass(_Fn nfWorkFun, _Args... args)
        {        
            NewRealExcuteFun* lpfWorkFun = new NewRealExcuteFun(bind(nfWorkFun, args...));
            return CreateThread(nullptr, 0, CThreadActuator::ThreadDispatch, (PVOID)lpfWorkFun, 0, nullptr);
        }
        
        // 真正的线程函数,间接调用类成员函数
        static DWORD WINAPI ThreadDispatch(PVOID npParam)
        {
            if (nullptr == npParam)
                return 0;
            else
            {
                NewRealExcuteFun* lpfWorkFun = (NewRealExcuteFun*)npParam;
                DWORD ldwRet = (*lpfWorkFun)();
                delete lpfWorkFun;
                lpfWorkFun = NULL;
                return ldwRet;
            }
        }
    };
}

而后发现这种方式彷佛连this指针都不须要传入了,也就是说对于除了类成员函数作线程函数的状况,其余普通函数也能够直接使用了。

调用方式为:

ThreadInClass::CThreadActuator::StartThreadInClass(&AddInThread, i, j);

与std::thread的区别就是,我这里函数的返回类型必须是DWORD,由于在ThreadDispatch函数里我须要返回这个函数的返回值。欧了,这是最终版本了。

至此再也没有新的版本了- -。。

 

最后附上代码连接:https://github.com/monotone/ThreadInClass.git

相关文章
相关标签/搜索