NT_SYSTEM
NT_SYSTEM

Reputation: 377

Issues while making a java like runnable class in C++

I've been trying to create a runnable class in order to interface multi-threaded classes just like java-folks do. But I can't seem to use _beginthread with the virtual function run. I'm getting the following error:

'beginthread' : cannot convert parameter 1 from 'void (_cdecl Runnable::* )(void *)' to 'void (__cdecl *)(void *)'

#include "CriticalSection.h"
#include <stdio.h>
#include <conio.h>
#include <process.h>

class Runnable{
private:
    Runnable() { _beginthread(&Runnable::Run,0,(void*)0); }
    ~Runnable();
    virtual void __cdecl Run ( void* ) = 0;
};

int main(){
    //CriticalSection crt;
    //ErrorHandler err;
    //LockSection lc(&crt,&err);

    while(!kbhit());
    return 0;
}

Upvotes: 3

Views: 3060

Answers (4)

There are two approaches to implementing multithreading libraries, both of them used in Java, with slightly different variants for C++. Since you seem to know Java, I will discuss the approaches there:

Deriving from Thread

In this approach, there is a base class that represents a thread, from which you inherit and implement a method that will be called when the thread starts. The base thread class offers control over the thread, and derived classes implement a run (or equivalent) method. The base thread class must not start the actual thread, but let user code start it through a member method start. The reason for this is that if the base thread class started the thread, the newly spawned thread might try to call the overridden method before the full object is created. This has the nasty side effect of calling the overrider in the base thread class in C++, or calling the final overrider of the run method on a yet uninitialized object in Java. --Unsafe in both cases.

Runnable interface

To reduce the possibility of causing undefined behavior, and to separate responsibilities, the second approach distinguishes thread control from the implementation of the code that is to be run in the newly spawned thread. A thread class is created, but not meant to be used as a base class. Rather an interface is offered for Runnable objects. User code will derive from that interface and pass an object to the thread class. The design ensures that the previous error cannot be done, as the Runnable must be fully created before passing it to the thread.

Modern C++ approach to the Runnable interface

The new standard threading libraries (and boost thread) offer an approach similar to the Runnable interface, with a couple of differences. The first is that user code does not need to fullfill a concrete interface but can actually tell the threading library what method of the class is to be run and with what arguments. This is implemented by applying type erasure in the constructor of the thread class, where the exact type of the user arguments are erased for later use inside the thread. This has the same advantages as the Runnable version --ensures that the object is fully created before the thread is spawned. At the same time, it removes the requirement of having to implement an exact interface, which means that you will be able to use a free function, or a member function of a class together with the instance on which that member function is to be called, or a functor that implements operator()...

If you are going to be coding in C++ I recommend that you use the last approach, as that is the most flexible for user code, and at the same time it is safer than what you are trying to do. That is, do not reinvent the wheel, there are good well thought C++ threading libraries (C++11 if you have it, boost if you don't --or Poco, or ACE...) that will avoid many of the pitfalls.

One really important bit to remember is that, whatever the approach you follow, you must ensure that the new thread does not try to call a virtual function in an object that is not fully created, as that will cause Undefined Behavior.

Upvotes: 6

R. Martinho Fernandes
R. Martinho Fernandes

Reputation: 234584

Even if what you wrote compiled as you expected, it wouldn't behave correctly. You are starting a thread in your constructor that will call a pure virtual function on this. But virtual dispatch while constructors are running does not dispatch to classes more derived that the one that is running the constructor.

If that code compiled, you would have a race condition: the virtual call could occur before the constructor of Runnable ended, or while a subclass constructor was running, or after they all ran. Each of these would have a different outcome, and the first one would probably crash.

Upvotes: 3

Antonio P&#233;rez
Antonio P&#233;rez

Reputation: 6992

You need the function to be passed to _beginthread to be static.

I suggest you read this article by Herb Sutter and try to implement the Active Object Pattern.

Upvotes: 2

Cat Plus Plus
Cat Plus Plus

Reputation: 129894

Use Boost.Thread or C++11 std::thread. Pointers to members are different than ordinary pointers to functions, so you cannot pass them to a C library (they require this pointer to be present, and library cannot handle it, because there is no such thing in C).

Upvotes: 1

Related Questions