Pointer to member function

D

Dan Smithers

I want to implement a C++ wrapper to C code that requires a function
pointer to be passed in. Specifically, I want a wrapper for pthread that
clients can use as a base class.

Is there a way of passing a non-static member function in that will do
this?

In the following code, if I use the static k_main then it builds and
runs, but the derived class uses the implementation in CMyThread.

If I use a virtual member function, I get a compile error:
thread2.cpp:21: error: argument of type ‘void* (CMyThread::)(void*)’
does not match ‘void* (*)(void*)’

Presumably this is because the member function pointer still expect the
this parameter when it is called.

I have thought of an alternative way that requires the CMyThread
constructor to pass the this pointer as an additional argument that is
then used in k_main to call the member function. It just looks really
horrible.


#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

class CMyThread
{
protected:
pthread_t m_thread;
void *m_args;

static void *k_main( void *ptr );
virtual void *m_main( void *ptr );
public:
CMyThread(char *name);
virtual ~CMyThread();
};

CMyThread::CMyThread(char *args)
: m_args(args)
{
int rc = pthread_create( &m_thread, NULL, m_main, (void *)args);
}

CMyThread::~CMyThread()
{
pthread_join( m_thread, NULL);
}

void *CMyThread::k_main(void *args)
{
char *message;
message = (char *) args;
printf("Base Static %s \n", message);
}

void *CMyThread::m_main(void *args)
{
char *message;
message = (char *) args;
printf("Base Virtual %s \n", message);
}

class CMyDerivedThread : public CMyThread
{
protected:
static void *k_main( void *ptr );
virtual void *m_main( void *ptr );

public:
CMyDerivedThread(char *name);
virtual ~CMyDerivedThread();
};

CMyDerivedThread::CMyDerivedThread(char *args)
: CMyThread(args)
{
}

CMyDerivedThread::~CMyDerivedThread()
{
}

void *CMyDerivedThread::k_main(void *args)
{
char *message;
message = (char *) args;
printf("Derived Static %s \n", message);
}

void *CMyDerivedThread::m_main(void *args)
{
char *message;
message = (char *) args;
printf("Derived Virtual %s \n", message);
}

main()
{
pthread_t thread1, thread2;
char *message1 = "Thread 1";
char *message2 = "Thread 2";

CMyThread t1(message1);

CMyThread t2(message2);

CMyDerivedThread("Thread 3");

exit(0);
}
 
D

Dan Smithers

kamit said:
Check out boost::thread.

Thanks, I've bookmarked the FAQ now (until Firefox eats the list).
I've done it by passing a this to a static wrapper.#

dan
 
J

James Kanze

I want to implement a C++ wrapper to C code that requires a
function pointer to be passed in. Specifically, I want a
wrapper for pthread that clients can use as a base class.
Is there a way of passing a non-static member function in that
will do this?

No. There's not even a way of passing a static member function.

First, of course, the calling conventions, and even the calling
syntax, of a non-static member function is necessarily different
from that of a static member or a free function; when calling a
non-static member, the compiler must associate an object with
the call. Obviously, there's no way that a C function can do
this. But there's also no guarantee that C and C++ use the same
calling conventions (and I've used compilers where they didn't).
And since the ``extern "C"'' function is going to call the
function whose address you pass it using the C calling
conventions, you can only pass it a pointer to a function which
uses the C calling conventions: a function declared ``extern
"C"'' in C.

In the case of functions like pthread_create, which also take a
void*, be very careful about your conversions to and from void*
as well. Once you've got the void*, the only thing you can do
with it is cast it back to the exact type which was converted to
get it. In particular, if you call pthread_create with a
Derived* as the last argument, then in the start up function,
you must cast it back to Derived*; casting it to Base* results
in undefined behavior.
In the following code, if I use the static k_main then it
builds and runs, but the derived class uses the implementation
in CMyThread.
If I use a virtual member function, I get a compile error:
thread2.cpp:21: error: argument of type ?void*
(CMyThread::)(void*)? does not match ?void* (*)(void*)?
Presumably this is because the member function pointer still
expect the this parameter when it is called.

Member function pointers typically have a completely different
layout from non-member function pointers, precisely because they
must be able to handle virtual functions. (If you take the
address of a virtual &Base::f, and call it through a Derived* p,
(p->*f)(), or through a Base* which actually points to a
Derived, it is Derived::f() which will be called.)
I have thought of an alternative way that requires the
CMyThread constructor to pass the this pointer as an
additional argument that is then used in k_main to call the
member function. It just looks really horrible.

It's the standard procedure. It is, in fact, the exact reason
why the void* is there in pthread_create (and most other
functions of its ilk). Calling a member function requires a
user supplied object. The purpose of the void* is to allow the
user to pass a pointer to an "object" (which, of course, can be
anything the user wants; boost::thread passes the address of a
Boost functional object, for example, and in some very special
cases, I've used it to pass a small integer, with some pretty
hairy reinterpret_casting).
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
class CMyThread

(Just an aside, but a single capital C prefix is the convention
for MFC classes. I'd avoid it unless I was actually
implementing MFC.)
{
protected:
pthread_t m_thread;
void *m_args;
static void *k_main( void *ptr );
virtual void *m_main( void *ptr );
public:
CMyThread(char *name);
virtual ~CMyThread();
};
CMyThread::CMyThread(char *args)
: m_args(args)
{
int rc = pthread_create( &m_thread, NULL, m_main, (void *)args);

If this compiles, your compiler is broken. And replacing m_main
with k_main shouldn't change anything here. You need a
separate free function:

extern "C" void*
threadStarter( void* args )
{
static_cast< CMyThread* >( args )->m_main() ;
}

And since you're dealing with an object, you don't need the
additional args---they can just be member variables of the
object.

Finally, of course, you can't call pthread_create from a
constructor of a base class without encuring race conditions; as
a general rule, you should never call it from the constructor of
a "thread" object which you expect run.
CMyThread::~CMyThread()
{
pthread_join( m_thread, NULL);

pthread_join can block. I'm not sure its a good idea to call it
from a destructor. (Destructors are called during stack
walkback, in case of an exception. Which is generally a context
where you don't want to block for an indefinite time.)

The usual solution for this sort of thing is to separate the
thread from what it does. Not only does it work, but it's a lot
cleaner in the long run. Thus, you might end up with something
like:

class Executable
{
public:
virtual ~Executable() {}
virtual void run() = 0 ;

private:
Executable( Executable const& ) ;
Executable& operator=( Executable const& ) ;
};

class Thread
{
public:
explicit Thread( Executable* exec ) ;
~Thread() ;
Executable* join() ;

private:
Thread( Thread const& ) ;
Thread& operator=( Thread const& ) ;

private:
Executable* myExecutable ;
pthread_t threadId ;
bool isJoined ;
} ;

// ...
extern "C"
void*
executableThreadStarter(
void* theExecutable )
{
static_cast< Executable >( theExecutable )->run() ;
return theExecutable ;
}

Thread::Thread(
Executable* exec )
: myExecutable( exec )
, isJoined( false )
{
pthread_start(
&threadId, NULL, &executableThreadStarter,
myExecutable ) ;
}

Thread::~Thread()
{
assert( isJoined ) ;
}

Executable*
Thread::join()
{
assert( ! isJoined ) ;
void* dummy ;
pthread_join( threadId, &dummy ) ;
return myExecutable ;
}

(This obviously needs far more error handling.)

Client code then derives from Executable.
 
D

Dan Smithers

Thanks for that James,

James said:
If this compiles, your compiler is broken.

It didn't
And replacing m_main
with k_main shouldn't change anything here.

It works with g++.
You need a
separate free function:

extern "C" void*
threadStarter( void* args )
{
static_cast< CMyThread* >( args )->m_main() ;
}

As it worked, I hadn't really considered the difficulties that other
compilers might cause.
And since you're dealing with an object, you don't need the
additional args---they can just be member variables of the
object.

Finally, of course, you can't call pthread_create from a
constructor of a base class without encuring race conditions; as
a general rule, you should never call it from the constructor of
a "thread" object which you expect run.



pthread_join can block. I'm not sure its a good idea to call it
from a destructor. (Destructors are called during stack
walkback, in case of an exception. Which is generally a context
where you don't want to block for an indefinite time.)

I'll have to think of a way of tidying up if exceptions are thrown.
The usual solution for this sort of thing is to separate the
thread from what it does. Not only does it work, but it's a lot
cleaner in the long run. Thus, you might end up with something
like:

I had come up with something similar, but in reverse.

I had a virtual class Thread that the user derives from, which contained
an implementation class ThreadImp that is created when the thread is
started.

I was still passing around void* arg lists without stopping to think
about whether I really needed to. (In the latest version I stored a void
* arg in the Thread class.

thanks for your suggestion.

dan
 
J

James Kanze

Dan said:
James Kanze wrote:
It didn't
It works with g++.

That's a known bug in g++.

[...]
I'll have to think of a way of tidying up if exceptions are
thrown.

It is a problem:). And I don't know of a good solution.
(Boost detaches the thread, which is even worse than doing the
join.) What you want to do is to force the other thread to
terminate as rapidly as possible, and then join. But at least
at present, pthread_cancel doesn't work (portably) in C++, and
even if it did, it's largely advisory, at least in some of it's
aspects.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,054
Latest member
TrimKetoBoost

Latest Threads

Top