Many operating systems and other subsystems (like GUI libraries) feature a special type of hook into those systems, named callbacks or callback functions. Upon initialization or by calling an API function you pass pointers to the callback into the subsystem, for later use. The problem with those functions is, since these subsystems are nowadays not yet OO, that they have no notion of what an object is. So if you want to have a callback object instead of a mere function, some OO magic is called for.
As an example, consider the BeginThread
API that many OSes have
in a quite similar form; we assume that it takes a pointer to the function
that provides the actual code for the newly created thread plus a data
pointer that is passed to that function as a startup parameter. Thus,
we end up with BeginThread (void (*thread_func) (void*), void* startup_data)
.
Now let's make a Thread
class of that.
What we want to have is an ABC (abstract base class) that you can inherit from,
creating specialized thread classes and in turn thread objects (i.e. actual
threads). The code of the thread is located in a pure virtual function
code
that is provided by the inherited class. code
is then
similar to the thread_func
parameter of the BeginThread
call,
but is a full-blown member function, not just a C function.
So, we get this interface for the Thread
class:
This might seem quite unusual to you (like having a protected constructor), but things will be explained in due course.
When we put the thread concept into a class, we have to consider lifetime. A
thread exists as long as the thread function does not return, thus the object
has to have the same lifetime. Because of this, an auto
thread object does not
make much sense; we insure that every thread object exists on the heap by making
the ctor protected and providing a static factory method create
for
thread objects in each derived class:
create
has to be added to every inherited class, returning an object
of that class.
Next, we need a run
method that actually starts the thread.
This can't be done in the ctor: when code
would be registered
as a thread of execution in the base class ctor, the superclass
would not yet be fully created and calling code
would be quite invalid
and dangerous. run
does its job by registering the dispatch
function as a thread, giving that thread the object pointer as a startup parameter;
since dispatch
is static, it has a prototype
that matches the void(*)(void*)
parameter of BeginThread
.
So finally, dispatch
is called and performs the step from a procedural
callback to the callback object:
A real-world thread class has to consider a few things we have ignored here. These things include:
Developed and tested on Windows NT, this is the
Have fun!