线程函数传 C++ 类实例指针惯用法

Posted vector6_

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了线程函数传 C++ 类实例指针惯用法相关的知识,希望对你有一定的参考价值。

线程函数传 C++ 类实例指针惯用法

除了 C++ 11 的线程库提供了的 std::thread 类对线程函数签名没有特殊要求外,无论是 Linux 还是 Windows 的线程函数的签名都必须是指定的格式,即参数和返回值必须是规定的形式。**如果使用 C++ 面向对象的方式对线程函数进行封装,那么线程函数就不能是类的实例方法,即必须是类的静态方法。**那么,为什么不能是类的实例方法呢?我们以 Linux 的线程函数签名为例:

void threadFunc(void* arg);

假设,我们将线程的基本功能封装到一个Thread类中,部分代码如下:

class Thread
{
public:
    Thread();
    ~Thread();

    void start();
    void stop();

    void threadFunc(void* arg);
};

由于 threadFunc 是一个类实例方法,无论是类的实例方法还是静态方法,C++ 编译器在编译时都会将这些函数“翻译”成全局函数,即去掉类的域限制。对于实例方法,为了保证类方法的正常功能,C++ 编译器在翻译时,会将类的实例对象地址(也就是this指针)作为第一个参数传递给该方法,也就是说,翻译后的threadFunc 的签名变成了如下形式(伪代码):

void threadFunc(Thread* this, void* arg);

这样的话,就不符合线程函数签名要求了。因此如果一个线程函数作为类方法,只能是静态方法而不能是实例方法。

当然,如果是使用 C++ 11 的 std::thread 类就没有这个限制,即使类成员函数是类的实例方法也可以,但是必须显式地将线程函数所属的类对象实例指针(在类的内部就是 this 指针)作为构造函数参数传递给 std::thread,还是需要传递类的 this 指针,这在本质上是一样的,代码实例如下:

#include <thread>
#include <memory>
#include <stdio.h>

class Thread
{
public:
    Thread()
    {
    }

    ~Thread()
    {
    }

    void Start()
    {
        m_stopped = false;
        //threadFunc是类的非静态方法,所以作为线程函数,第一个参数必须传递类实例地址,即this指针
        m_spThread.reset(new std::thread(&Thread::threadFunc, this, 8888, 9999));
    }

    void Stop()
    {
        m_stopped = true;
        if (m_spThread)
        {
            if (m_spThread->joinable())
                m_spThread->join();
        }
    }

private:
    void threadFunc(int arg1, int arg2)
    {
        while (!m_stopped)
        {
            printf("Thread function use instance method.\\n");
        }      
    }

private:
    std::shared_ptr<std::thread>  m_spThread;
    bool                          m_stopped;
};

int main()
{
    Thread mythread;
    mythread.Start();

    while (true)
    {
        //权宜之计,让主线程不要提前退出
    }

    return 0;
}

上述代码中使用了 C++ 11 新增的智能指针 std::shared_ptr 类来包裹了一下 new 出来的 std::thread 对象,这样我们就不需要自己手动 delete 这个 std::thread 对象了。

综上所述,如果不使用 C++ 11 的语法,那么线程函数只能作为类的静态方法,且函数签名必须按规定的签名格式来。如果是类的静态方法,那么就没法访问类的实例方法了,为了解决这个问题,我们在实际开发中往往会在创建线程时将当前对象的地址(this 指针)传递给线程函数,然后在线程函数中,将该指针转换成原来的类实例,再通过这个实例就可以访问类的所有方法了。代码示例如下:

.h 文件代码如下:

/**
 * Thread.h
 */
#ifdef WIN32
//#include <windows.h>
typedef HANDLE THREAD_HANDLE ;
#else
//#include <pthread.h>
typedef pthread_t THREAD_HANDLE ;
#endif

/**定义了一个线程对象
*/
class  CThread  
{
public:
    /**构造函数
    */
    CThread();

    /**析构函数
    */
    virtual ~CThread();

    /**创建一个线程
    * @return true:创建成功 false:创建失败
    */
    virtual bool Create();

    /**获得本线程对象存储的线程句柄
    * @return 本线程对象存储的线程句柄线程句柄
    */
    THREAD_HANDLE GetHandle();

    /**线程睡眠seconds秒
    * @param seconds 睡眠秒数
    */
    void OSSleep(int nSeconds);

    void SleepMs(int nMilliseconds);

    bool Join();

    bool IsCurrentThread();

    void ExitThread();

private:    
#ifdef WIN32
    static DWORD WINAPI _ThreadEntry(LPVOID pParam);
#else
    static void* _ThreadEntry(void* pParam);
#endif

    /**虚函数,子类可做一些实例化工作
    * @return true:创建成功 false:创建失败
    */
    virtual bool InitInstance();

    /**虚函数,子类清楚实例
    */
    virtual void ExitInstance();

    /**线程开始运行,纯虚函数,子类必须继承实现
    */
    virtual void Run() = 0;

private:
     THREAD_HANDLE  m_hThread;    // 线程句柄 
     DWORD          m_IDThread;
};

.cpp 文件如下:

/**
 * Thread.cpp
 */
#include "Thread.h"

#ifdef WIN32
DWORD WINAPI CThread::_ThreadEntry(LPVOID pParam)
#else
void* CThread::_ThreadEntry(void* pParam)
#endif
{
    CThread *pThread = (CThread *)pParam;
    if(pThread->InitInstance())
    {
        pThread->Run();
    }

    pThread->ExitInstance();

    return NULL;
}

CThread::CThread()
{
    m_hThread = (THREAD_HANDLE)0;
    m_IDThread = 0;
}

CThread::~CThread()
{
}

bool CThread::Create()
{
    if (m_hThread != (THREAD_HANDLE)0)
    {
        return true;
    }
    bool ret = true;
#ifdef WIN32
    m_hThread = ::CreateThread(NULL,0,_ThreadEntry,this,0,&m_IDThread);
    if(m_hThread==NULL)
    {
        ret = false;
    }
#else
    ret = (::pthread_create(&m_hThread,NULL,&_ThreadEntry , this) == 0);
#endif
    return ret;
}

bool CThread::InitInstance()
{
    return true;
}

void CThread::ExitInstance()
{
}

void CThread::OSSleep(int seconds)
{
#ifdef WIN32
    ::Sleep(seconds*1000);
#else
    ::sleep(seconds);
#endif
}

void CThread::SleepMs(int nMilliseconds)
{
#ifdef WIN32
    ::Sleep(nMilliseconds);
#else
    ::usleep(nMilliseconds);
#endif
}

bool CThread::IsCurrentThread()
{
#ifdef WIN32
    return ::GetCurrentThreadId() == m_IDThread;
#else
    return ::pthread_self() == m_hThread;
#endif
}

bool CThread::Join()
{    
    THREAD_HANDLE hThread = GetHandle();
    if(hThread == (THREAD_HANDLE)0)
    {
        return true;
    }
#ifdef WIN32
    return (WaitForSingleObject(hThread,INFINITE) != 0);
#else
    return (pthread_join(hThread, NULL) == 0);
#endif
}

void CThread::ExitThread()
{
#ifdef WIN32
    ::ExitThread(0);
#else
#endif
}

上述代码 CThread 类封装了一个线程的常用的操作,使用宏 WIN32 来分别实现了 Windows 和 Linux 两个操作系统平台的线程操作。其中 InitInstance 和 ExitInstance 方法为虚函数,在继承 CThread 的子类中可以改写这两个方法,根据实际需要在线程函数正式业务逻辑前后做一些初始化和反初始化工作,而纯虚接口 Run 方法必须改写,改写成您的线程实际执行函数。

在线程函数中通过在创建线程时(调用 CreateThread 或 pthread_create 方法)时,将当前对象的 this 指针作为线程的函数的唯一参数传入,这样在线程函数中,可以通过线程函数参数得到对象的指针,通过这个指针就可以自由访问类的实例方法了。

以上是关于线程函数传 C++ 类实例指针惯用法的主要内容,如果未能解决你的问题,请参考以下文章

C ++将多个对象传递给线程中的函数

使用指向类私有方法的指针的命名参数惯用语

[ C++ ] 多态原理 多态

C++的单例模式与线程安全单例模式(懒汉/饿汉)

c++类的成员函数指针如何转为普通指针

C++11多线程第三篇:线程传参详解,detach()大坑,成员函数做线程参数