Encapsulating a Thread in a C++ Object

It’s a frequently asked question in C++ forums and newsgroups. Try to create a thread and pass an instance member function of a C++ class as the thread start routine and the compiler balks. What is the compiler complaining about? How can a thread be run within an instance of a class?

The Microsoft C runtime library (MSCRT) includes the _beginthreadex1 routine.

uintptr_t _beginthreadex(
    void *security,
    unsigned stack_size,
    unsigned ( __stdcall *start_address )( void * ),
    void *arglist,
    unsigned initflag,
    unsigned *thrdaddr
);

_beginthreadex expects a pointer to a function, a function that will be executed by the newly created thread. It’s the start_address parameter in the function definition above.

If a non-static member function of a class is passed to the _beginthreadex function as the start_address parameter, the code will fail to compile. (With the Microsoft compiler the error might be one of the following depending on the specific syntax used: C3867, C2276, or C2664.)

Members of a C++ class can be static or non-static. Static members are sometimes known as class members. Non-static members are sometimes known as instance members. Instance member functions operate on a specific instance of a class. They are able to do so because the C++ compiler provides instance member functions with a ‘this’ pointer as a hidden function argument. The hidden extra argument is the source of the compiler’s consternation. An instance member function can’t be passed as the start_address parameter.

Static member function don’t have the special ‘this’ pointer magic. Can a static member function be passed as the start_address? Yes. But if the goal is to create a class whose instances are separate threads, passing a static member function would seem at first to not be much of an advance towards a solution.

As a student I was told that static member functions can not access non-static data members or call non-static member functions. It’s a simplistic statement that implies a prohibition that doesn’t exist. Statics have no knowledge of any specific instances of a class. There’s no ‘this’ pointer in a static member function. But static functions have the same permissions on the class as non-static functions. In other words if a static function is given a ‘this’ pointer it can use the ‘this’ pointer to access the data and functions of a specific instance.

All that needs to be done is to arrange to pass a ‘this’ pointer to the static member function. _beginthreadex has an arglist parameter for passing arguments to the thread start routine. Below is example code that implements this technique.

A couple of things to note:

  • Data that the thread needs to operate on can be stored in the object instance. Only the ‘this’ pointer needs to be passed via the thread start routine.
  • The general technique of using a static member function to forward to a specific object instance can be applied to encapsulating any C language style callback.

Windows thread class

This code was written and tested with Visual Studio 2005.

#define WIN32_LEAN_AND_MEAN
#include <process.h>
#include <windows.h>
#include <iostream>

class thread
{
public:
    thread();
    virtual ~thread();

    const HANDLE& GetHandle() const { return m_hThread; }
    const bool wait() const;

private:
    // copy operations are private to prevent copying
    thread(const thread&);
    thread& operator=(const thread&);

    static unsigned __stdcall threadProc(void*);
    unsigned run();

    HANDLE m_hThread;
    unsigned m_tid;
};

thread::thread()
{
    m_hThread = reinterpret_cast<HANDLE>(
        ::_beginthreadex(
                0, // security
                0, // stack size
                threadProc, // thread routine
                static_cast<void*>(this), // thread arg
                0, // initial state flag
                &m_tid // thread ID
            )
        );
    if (m_hThread == 0)
    {
        throw std::exception("failed to create thread");
    }
}

thread::~thread()
{
    try    
    {
        ::CloseHandle(GetHandle());
    }
    catch(...)
    {
        // suppress any exception; dtors should never throw
    }
}

const bool thread::wait() const
{
    bool bWaitSuccess = false;
    // a thread waiting on itself will cause a deadlock
    if (::GetCurrentThreadId() != m_tid)
    {
        DWORD nResult = ::WaitForSingleObject(GetHandle(), INFINITE);
        // nResult will be WAIT_OBJECT_0 if the thread has terminated;
        // other possible results: WAIT_FAILED, WAIT_TIMEOUT,
        // or WAIT_ABANDONED
        bWaitSuccess = (nResult == WAIT_OBJECT_0);
    }
    return bWaitSuccess;
}

unsigned __stdcall thread::threadProc(void* a_param)
{
    thread* pthread = static_cast<thread*>(a_param);
    return pthread->run();
}

unsigned thread::run()
{
    std::cout << "Hello from thread" << std::endl;
    return 0;
}

int main(int argc, char* const argv[])
{
    try
    {
        std::cout << "Hello" << std::endl;

        thread thrd;
        thrd.wait();

        std::cout << "Done" << std::endl;
    }
    catch (std::exception& ex)
    {
        std::cerr << ex.what() << std::endl;
    }
    catch (...)
    {
        std::cerr << "unknown exception" << std::endl;
    }

    return 0;
}

pthreads thread class

Just so you don’t think I’m Windows-centric, below is a pthreads version of the example code. This example was written and tested in Xcode on Mac OS X.

#include <iostream>
#include <exception>
#include <pthread.h>

class thread
{
public:
    thread();
    virtual ~thread() { }

    const pthread_t& getTID() const { return m_tid; }
    bool join();

private:
    // copy operations are private to prevent copying
    thread(const thread&);                // no implementation
    thread& operator=(const thread&);    // no implementation

    static void* threadproc(void*);
    void* run();

    pthread_t m_tid;
};

thread::thread()
{
    int nerr = ::pthread_create(
            &m_tid,
            0,
            threadproc,
            static_cast<void*>(this)
        );
    if (nerr)
    {
        // for brevity a string instead of a custom exception
        throw std::string("failed to create thread");
    }
}

bool thread::join()
{
    // pthread_join will detect attempts to join a thread to itself
    int nerr = ::pthread_join(getTID(), 0);
    return (nerr == 0);
}

void* thread::threadproc(void* a_param)
{
    thread* pthread = static_cast<thread*>(a_param);
    return pthread->run();
}

void* thread::run()
{
    std::cout << "Hello from thread" << std::endl;
    return 0;
}

int main (int argc, char* const argv[])
{
    try
    {
        std::cout << "Hello" << std::endl;

        thread thrd;
        thrd.join();
        
        std::cout << "Done" << std::endl;
    }
    catch (std::string& ex)
    {
        std::cerr << ex << std::endl;
    }
    catch (std::exception& ex)
    {
        std::cerr << ex.what() << std::endl;
    }
    catch (...)
    {
        std::cerr << "unknown exception" << std::endl;
    }

    return 0;
}

Related Posts:
Disallowing Copying in a C++ Class

1_beginthreadex is a wrapper that performs some library specific housekeeping and calls the Win32 API’s CreateThread function. If the MS C runtime library is being used, it’s best to use the wrapper and not call CreateThread directly.