Boost logo

Boost :

From: Jesse Jones (jesjones_at_[hidden])
Date: 2000-10-15 20:34:53


I've been using callback objects derived from the code in Rich Hickey's
C++ Report article for quite a while now. In a nutshell, these are
non-tempate classes that can transparently hold pointers to either
free-standing functions or member functions. They're very similar to C#
events or the new callback stuff in Eiffel. They seem to me to be useful
enough to merit inclusion in boost so I thought I'd see what you guys
think.

Here are two applications of the callback classes that will, I hope, show
how flexible they are:

1) The first is quite simple: a timer class that calls functions at a
specified frequency. Lacking callbacks I think the best way to handle
this is to provide a mixin class that can be multiply inherited from for
those classes that need a timer. This works pretty well for simple cases,
but gets uglier for more complex cases (eg a base class might want a
timer and a derived class might want a different timer).

OTOH callbacks provide a simpler alternative that works nicely for both
the simple and complex cases. For example, in my framework I have a
COM-like interface that manages timers:

class ITimer : public XUnknown {

//-----------------------------------
// API
//
public:
    virtual void AddCallback(XCallback0<void> callback, MilliSecond freq)
= 0;
                 // Frequency is the interval at which the callback wants
to be
                 // called. Note that the actual interval may
occasionally be
                 // larger than freq (but it won't be smaller).

    virtual void ChangeFrequency(XCallback0<void> callback, MilliSecond
freq) = 0;
                 // Updates the frequency of a previously added callback.

    virtual void RemoveCallback(XCallback0<void> callback) = 0;
                 // Note that it's ok to call this if the callback hasn't
been added.
    
//-----------------------------------
// Internal API
//
public:
    virtual void Tickle() = 0;
                 // Called by the app.
};

Client code can then add a timer with code like this:

    ITimerPtr timer(this);
    XCallback0<void> callback(this, &CApplication::DoRender);
    timer->AddCallback(callback, kIdleFreq);

This is much nicer: you don't have to mixin new classes to support
timers, you can name the timer function anything you like, and it's
trivial to support multiple timers on the same object.

2) Here's a more complex example. Many apps need to support undo. This is
usually done using the Command pattern. Unfortunately this typically
means that apps wind up with a profusion of tiny command classes which
delegate all the real work to someone else (eg the model in an MVC app).

But callbacks allow you to get rid of all or nearly all of these command
classes. All you need is a version of the command class that takes
callbacks. For example, I have the following interface on one of my
framework undo command classes:

class IUndoCallbackAction : public XUnknown {

public:
    virtual void SetCallbacks(const XCallback0<void>& doIt, const
XCallback0<void>& undoIt) = 0;
    
    virtual void SetCallbacks(const XCallback0<void>& doIt, const
XCallback0<void>& undoIt, const XCallback0<void>& redoIt) = 0;
};

When clients need to do perform an undoable operation they execute code
like this:

void CDocMenuHandler::DoSetPalette(const std::wstring& name)
{
    // Get the new palette
    IPalettesPtr palettes(L"Application");
    XColorTable colors = palettes->Get(name);
    
    // Build the callbacks that set and restore the palette
    IDocInfoPtr info(this);
    XCallback3<void, XColorTable, std::wstring, bool> method(info.Get(),
&IDocInfo::SetPalette);
    XCallback0<void> doIt = Adapt0(method, colors, name, true);
    XCallback0<void> undoIt = Adapt0(method, info->GetPalette(),
info->GetPaletteName(), kDirtyDoc);

    // Initialize the undo action
    IUndoActionPtr action(L"Undo Callback Action");
    action->Init(LoadAppString(L"Set Palette"));
    
    IUndoCallbackActionPtr undoer(action);
    undoer->SetCallbacks(doIt, undoIt);
    
    // Put the action on the command queue
    ICommandQueuePtr queue(L"Application");
    ICommandPtr command(action);
    queue->Post(command);
}

    XCallback3<void, XColorTable, std::wstring, bool> method(info.Get(),
&IDocInfo::SetPalette);
is used to build a callback to an IDocInfo method that returns void and
has three arguments: a color table. a string, and a bool.

    XCallback0<void> doIt = Adapt0(method, colors, name, true);
Adapt0 is used to convert the three argument callback into a zero
argument callback. It does this by saving off the given arguments so they
can be used when the new callback is invoked. There are also other
versions of the Adapt functions that leave some of the arguments unbound.
For example, Adapt1 could have been used to fix just two of the arguments.

    XCallback0<void> undoIt = Adapt0(method, info->GetPalette(),
info->GetPaletteName(), kDirtyDoc);
does exactly the same thing as the doIt case.

The undo command itself is very simple:

class XUndoCallbackAction : public XUndoAction, public
IUndoCallbackAction {

    typedef XUndoAction Inherited;

//-----------------------------------
// Initialization/Destruction
//
public:
    virtual ~XUndoCallbackAction();
    
                XUndoCallbackAction(XBoss* boss);
                        
    virtual void SetCallbacks(const XCallback0<void>& doIt, const
XCallback0<void>& undoIt);
    
    virtual void SetCallbacks(const XCallback0<void>& doIt, const
XCallback0<void>& undoIt, const XCallback0<void>& redoIt);

//-----------------------------------
// Inherited API
//
public:
    virtual void OnDo();

    virtual void OnUndo();

    virtual void OnRedo();
    
//-----------------------------------
// Member Data
//
protected:
    XCallback0<void> mDoIt;
    XCallback0<void> mUndoIt;
    XCallback0<void> mRedoIt;
};

void XUndoCallbackAction::SetCallbacks(const XCallback0<void>& doIt,
const XCallback0<void>& undoIt)
{
    mDoIt = doIt;
    mUndoIt = undoIt;
    mRedoIt = doIt;
}

void XUndoCallbackAction::OnDo()
{
    mDoIt();
}

// etc

If there's interest in these callback classes I'd be happy to upload them
to the files section so you guys can take a closer look at them.

  -- Jesse


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk