Boost logo

Boost :

From: Steven Mauceri (sm_at_[hidden])
Date: 2003-01-23 12:07:58


Hi Andreas,

An undo library seems to be on a boundary between patterns and library
functions, so im interested in seeing how something like an undo library
plays out.

Some things ive come across include:

- The need to keep objects utilized in doIt() alive until they leave the
undo list (this sometimes is even beyond serialization). Implementers
of command objects frequently use some type of shared_ptr to keep things
alive after use (until they fall off the undo queue).

- The need to keep things simple, as Steven Kirk noted. This competes
with the need to employ a more complex (as applications grow) command /
command-manager interface. Providing a command-base-imp might be a way
to satisfy both users.

- The ability for commands that can not support undo to inform the
manager (sending an email, committing transactions, or a developer
testing new objects are examples of commands that do not support undo).
When these commands are executed, they clear the undo-stack (this can be
augmented in a UI app with a message informing the user they are about
to execute an undoable command).

- The need to support various command managers. People may want to
extend the behavior of the undo-manager, but often start out with a
default manager (templatizing the manager's container might also be an
option).

The code below might help illustrate the suggestions mentioned above
(and by S.Kirk in the previous email) for the command class (The manager
interface is a separate discussion probably :-).

- Steve

struct command
{
 virtual void doIt() = 0; // Called first time
 virtual void undoIt() = 0; // Called to undo
 virtual void redoIt() = 0; // Called after first time

 virtual bool supportsUndo() = 0;
 virtual bool supportsRedo() = 0;

 virtual ~command() {}
};

struct commandBinder : public command
{
 commandBinder( const boost::function<void> &doItFn,
  const boost::function<void> &redoItFn =
&commandBinder::redoUnsupported,
  const boost::function<void> &undoItFn =
&commandBinder::undoUnsupported )
   : m_doItFunction( doItFn ),
     m_undoItFunction( undoItFn ),
     m_redoItFunction( redoItFn ) {}

 virtual void invokeCommand(
   const boost::function<void> &fn, const std::string &msg )
 {
  if ( fn.empty() )
   throw undo_exception( msg.c_str() );
  else
   fn();
 }

 virtual void doIt()
 { invokeCommand( m_doItFunction, "" ); }
 
 virtual void redoIt()
 { invokeCommand( m_redoItFunction, "redoIt unsupported" ); }

 virtual void undoIt()
 { invokeCommand( m_undoItFunction, "undoIt unsupported" ); }
        
 virtual bool supportsUndo() { return !m_undoItFunction.empty(); }

 virtual bool supportsRedo() { return !m_redoItFunction.empty(); }

 boost::function<void> m_doItFunction;
 boost::function<void> m_redoItFunction;
 boost::function<void> m_undoItFunction;

 static void undoUnsupported() {}
 static void redoUnsupported() {}

 virtual ~commandBinder() {}
};


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