Re: Pointer to member function
On Jun 26, 4:12 pm, Dan Smithers <dsmith...@talktalk.net> wrote:
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.
--
James Kanze (GABI Software) email:james.kanze@gmail.com
Conseils en informatique orient=E9e objet/
Beratung in objektorientierter Datenverarbeitung
9 place S=E9mard, 78210 St.-Cyr-l'=C9cole, France, +33 (0)1 30 23 00 34