初歩的な実装

Undo コマンド

まずは Undo マネージャに登録するコマンドのインターフェイスを決めましょう
Undo マネージャに登録するコマンドはつまり Undo 可能なコマンドということですが、この Undo 可能なコマンドはアプリケーション側が実装するものです
そしてこのコマンドは少なくとも Undo マネージャから Undo と Redo の通知を受け取れるようにしておく必要があります
このコマンドはどのような形式でもいいのですが、ここでは次のようなインターフェイスを使うことにしましょう

#define	UNDOREDO_DETACHED	0
#define	UNDOREDO_ATTACHED	1
#define	UNDOREDO_UNDO	2
#define	UNDOREDO_REDO	3

struct IRestoreObj {
	virtual uint32_t	add_ref() = 0;
	virtual uint32_t	release() = 0;
	virtual void	invoke( uint32_t	id, uint32_t	uMsg ) = 0;
};

簡単なラッパも書いておくと、このインターフェイスを毎回一から実装しなくてもよくて楽です

class CRestoreObj : public IRestoreObj {
protected:
	virtual void	undo(){}
	virtual void	redo(){}
public:
	virtual uint32_t	add_ref(){
		return	( uint32_t )++m_cRef;
	}
	virtual uint32_t	release(){
		int32_t	cRef = --m_cRef;
		if( cRef == 0 ){
			delete this;
			return	0;
		}
		return	( uint32_t )cRef;
	}
	virtual void	invoke( uint32_t, uint32_t	uMsg ){
		if( uMsg == UNDOREDO_DETACHED ){
			release();
		}
		else if( uMsg == UNDOREDO_ATTACHED ){
			add_ref();
		}
		else if( uMsg == UNDOREDO_UNDO ){
			undo();
		}
		else if( uMsg == UNDOREDO_REDO ){
			redo();
		}
	}
	CRestoreObj():
	m_cRef(1)
	{
	}
	virtual ~CRestoreObj(){
	}
private:
	int32_t	m_cRef;
	
	CRestoreObj( const CRestoreObj& );
	CRestoreObj&	operator=( const CRestoreObj& );
};

ここでは通知を一つのメソッドにまとめたインターフェイスを使いますが、別にそれぞれ別のメソッドとするインターフェイスでも構いません

Undo コマンド管理クラスの実装

#define	INVALID_COMMAND_ID	(( uint32_t )-1)

class cundoredoitem {
	uint32_t	m_id;
	IRestoreObj*	m_pRestoreObj;
	
	bool	dispatch( uint32_t	uMsg );
public:
	void	detach();
	bool	attach( uint32_t	id, IRestoreObj*	pRestoreObj );
	bool	get_object( IRestoreObj**	ppRestoreObj );
	bool	undo();
	bool	redo();
	
	bool	operator==( uint32_t	id ) const;
	cundoredoitem&	operator=( const cundoredoitem&	rhs );
	
	cundoredoitem();
	cundoredoitem( uint32_t	id, IRestoreObj*	pRestoreObj );
	cundoredoitem( const cundoredoitem&	rhs );
	~cundoredoitem();
};
inline bool	cundoredoitem::operator==( uint32_t	id ) const {
	return	( m_id == id );
}
inline cundoredoitem&	cundoredoitem::operator=( const cundoredoitem&	rhs ){
	if( this != &rhs ){
		m_id = rhs.m_id;
		m_pRestoreObj = rhs.m_pRestoreObj;
	}
	return	*this;
}
inline cundoredoitem::cundoredoitem():
m_id(INVALID_COMMAND_ID),
m_pRestoreObj(NULL)
{
}
inline cundoredoitem::cundoredoitem( uint32_t	id, IRestoreObj*	pRestoreObj ):
m_id(id),
m_pRestoreObj(pRestoreObj)
{
}
inline cundoredoitem::cundoredoitem( const cundoredoitem&	rhs ):
m_id(rhs.m_id),
m_pRestoreObj(rhs.m_pRestoreObj)
{
	if( m_pRestoreObj ){
		m_pRestoreObj->add_ref();
	}
}
inline cundoredoitem::~cundoredoitem(){
	if( m_pRestoreObj ){
		m_pRestoreObj->release();
		m_pRestoreObj = NULL;
	}
}
inline bool	cundoredoitem::dispatch( uint32_t	uMsg ){
	bool	result = true;
	if( !m_pRestoreObj ){
		result = false;
	}
	if( result ){
		m_pRestoreObj->invoke( m_id, uMsg );
	}
	return	result;
}
inline void	cundoredoitem::detach(){
	if( m_pRestoreObj ){
		dispatch( UNDOREDO_DETACHED );
		m_pRestoreObj->release();
		m_pRestoreObj = NULL;
	}
	m_id = INVALID_COMMAND_ID;
}
inline bool	cundoredoitem::attach( uint32_t	id, IRestoreObj*	pRestoreObj ){
	bool	result = true;
	if(( m_id != id )
	 ||( m_pRestoreObj != pRestoreObj )
	){
		detach();
		if( pRestoreObj ){
			m_id = id;
			pRestoreObj->add_ref();
			m_pRestoreObj = pRestoreObj;
			result = dispatch( UNDOREDO_ATTACHED );
			if( !result ){
				m_pRestoreObj->release();
				m_pRestoreObj = NULL;
				m_id = INVALID_COMMAND_ID;
			}
		}
	}
	return	result;
}
inline bool	cundoredoitem::get_object( IRestoreObj**	ppRestoreObj ){
	if( ppRestoreObj ){
		*ppRestoreObj = NULL;
	}
	bool	result = true;
	if( !ppRestoreObj ){
		result = false;
	}
	else if( !m_pRestoreObj ){
		result = false;
	}
	if( result ){
		m_pRestoreObj->add_ref();
		*ppRestoreObj = m_pRestoreObj;
	}
	return	result;
}
inline bool	cundoredoitem::undo(){
	return	dispatch( UNDOREDO_UNDO );
}
inline bool	cundoredoitem::redo(){
	return	dispatch( UNDOREDO_REDO );
}

Undo リスト管理クラスの実装

ここではコマンドのリストを Undo用と Redo用の2つに分けて実装しています

class cundoredo {
	std::deque<cundoredoitem>	m_redo;
	std::deque<cundoredoitem>	m_undo;
	uint32_t	m_id;
public:
	bool	add_object( IRestoreObj*	pRestoreObj, uint32_t*	pid );
	bool	delete_object( uint32_t	id );
	bool	get_object( uint32_t	id, IRestoreObj**	ppRestoreObj );
	bool	undo();
	bool	redo();
	bool	is_undo_available();
	bool	is_redo_available();
	
	cundoredo();
	~cundoredo();
private:
	cundoredo( const cundoredo& );
	cundoredo&	operator=( const cundoredo& );
};
cundoredo::cundoredo():
m_redo(),
m_undo(),
m_id(INVALID_COMMAND_ID)
{
}
cundoredo::~cundoredo(){
	cundoredo::delete_object( INVALID_COMMAND_ID );
}
bool	cundoredo::add_object( IRestoreObj*	pRestoreObj, uint32_t*	pid ){
	if( pid ){
		pid = 0;
	}
	bool	result = true;
	if( !pRestoreObj ){
		result = false;
	}
	if( result ){
		cundoredoitem	item;
		result = item.attach( ++m_id, pRestoreObj );
		if( result ){
			m_undo.push_back( item );
			for( std::deque<cundoredoitem>::iterator	p = m_redo.begin(); p != m_redo.end(); ++p ){
				p->detach();
			}
			m_redo.clear();
			result = m_undo.rbegin()->redo();
		}
	}
	return	result;
}
bool	cundoredo::get_object( uint32_t	id, IRestoreObj**	ppRestoreObj ){
	if( ppRestoreObj ){
		*ppRestoreObj = NULL;
	}
	bool	result = true;
	if( !ppRestoreObj ){
		result = false;
	}
	if( result ){
		std::deque<cundoredoitem>::iterator	p = std::find( m_undo.begin(), m_undo.end(), id );
		if( p == m_undo.end()){
			result = false;
		}
		else{
			p = std::find( m_redo.begin(), m_redo.end(), id );
			if( p == m_redo.end()){
				result = false;
			}
		}
		if( result ){
			result = p->get_object( ppRestoreObj );
		}
	}
	return	result;
}
bool	cundoredo::delete_object( uint32_t	id ){
	bool	result = true;
	if( id == INVALID_COMMAND_ID ){
		for( std::deque<cundoredoitem>::iterator	p = m_redo.begin(); p != m_redo.end(); ++p ){
			p->detach();
		}
		m_redo.clear();
		for( std::deque<cundoredoitem>::iterator	p = m_undo.begin(); p != m_undo.end(); ++p ){
			p->detach();
		}
		m_undo.clear();
		m_id = INVALID_COMMAND_ID;
	}
	else{
		result = false;
		std::deque<cundoredoitem>::iterator	p = std::find( m_undo.begin(), m_undo.end(), id );
		if( p != m_undo.end()){
			p->detach();
			m_undo.erase( p );
			result = true;
		}
		else{
			p = std::find( m_redo.begin(), m_redo.end(), id );
			if( p != m_redo.end()){
				p->detach();
				m_redo.erase( p );
				result = true;
			}
		}
	}
	return	result;
}
bool	cundoredo::is_undo_available(){
	return	!m_undo.empty();
}
bool	cundoredo::is_redo_available(){
	return	!m_redo.empty();
}
bool	cundoredo::undo(){
	if( !m_undo.empty()){
		std::deque<cundoredoitem>	undo;
		undo.push_back( m_undo.back());
		m_undo.pop_back();
		for( std::deque<cundoredoitem>::reverse_iterator	p = undo.rbegin(); p != undo.rend(); ++p ){
			m_redo.push_back( *p );
		}
		for( std::deque<cundoredoitem>::iterator	p = undo.begin(); p != undo.end(); ++p ){
			p->undo();
		}
	}
	return	true;
}
bool	cundoredo::redo(){
	if( !m_redo.empty()){
		std::deque<cundoredoitem>	redo;
		redo.push_back( m_redo.back());
		m_redo.pop_back();
		for( std::deque<cundoredoitem>::reverse_iterator	p = redo.rbegin(); p != redo.rend(); ++p ){
			m_undo.push_back( *p );
		}
		for( std::deque<cundoredoitem>::reverse_iterator	p = redo.rbegin(); p != redo.rend(); ++p ){
			p->redo();
		}
	}
	return	true;
}

テストコード

void	debug_printf( const char*	format,... ){
	char	temp[256] = {};
	va_list	ap;
	va_start( ap, format );
	_vsnprintf( temp, elementsof( temp ), format, ap );
	va_end( ap );
	OutputDebugStringA( temp );
}
class CRestoreObj_string_push_back : public CRestoreObj {
	std::string&	m_string;
	char	m_c;
	uint8_t	:8;
	uint8_t	:8;
	uint8_t	:8;
	
	void	push_back( char	c ){
		m_string.push_back( c );
	}
	void	pop_back(){
		if( !m_string.empty()){
			m_string.erase( m_string.size()-1 );
		}
	}
public:
	void	undo(){
		std::string	string_prev = m_string;
		pop_back();
		debug_printf( "undo: \"%s\"\"%s\"\n", string_prev.c_str(), m_string.c_str());
	}
	void	redo(){
		std::string	string_prev = m_string;
		push_back( m_c );
		debug_printf( "redo: \"%s\"\"%s\"\n", string_prev.c_str(), m_string.c_str());
	}
	
	CRestoreObj_string_push_back( std::string&	s, char	c ):
	CRestoreObj(),
	m_string(s),
	m_c(c)
	{
	}
	virtual ~CRestoreObj_string_push_back(){
	}
private:
	CRestoreObj_string_push_back();
	CRestoreObj_string_push_back( const CRestoreObj_string_push_back& );
	CRestoreObj_string_push_back&	operator=( const CRestoreObj_string_push_back& );
};
inline void	string_push_back( cundoredo&	undoredo, std::string&	s, char	c, uint32_t*	pid = NULL ){
	IRestoreObj*	p = new CRestoreObj_string_push_back( s, c );
	undoredo.add_object( p, pid );
	p->release();
}
int	main(){
	std::string	s;
	cundoredo	undoredo;
	string_push_back( undoredo, s, 'A' );
	string_push_back( undoredo, s, 'B' );
	string_push_back( undoredo, s, 'C' );
	undoredo.undo();
	undoredo.undo();
	undoredo.redo();
	string_push_back( undoredo, s, 'D' );
	return	0;
}

出力

redo: "" → "A"
redo: "A" → "AB"
redo: "AB" → "ABC"
undo: "ABC" → "AB"
undo: "AB" → "A"
redo: "A" → "AB"
redo: "AB" → "ABD"