コマンドチェインの無効化に対応

例えば移動したディレクトリを記録しているときに、履歴に残っているディレクトリが途中で削除されてしまった場合には、そのディレクトリへ移動できないようにしたり、そのディレクトリを表す項目を淡色表示したい、というようなこともあるでしょう

そこで、登録されているコマンドが途中で無効になってしまったような場合には、そのコマンドを無視することができるようにしてみましょう

Undo コマンドの基底クラスの実装

#define	UNDOREDO_DETACHED	0
#define	UNDOREDO_ATTACHED	1
#define	UNDOREDO_UNDO		2
#define	UNDOREDO_REDO		3
#define	UNDOREDO_UNDO_BEGIN	4
#define	UNDOREDO_UNDO_END	5
#define	UNDOREDO_REDO_BEGIN	6
#define	UNDOREDO_REDO_END	7
#define	UNDOREDO_UNDO_ENABLED	8
#define	UNDOREDO_REDO_ENABLED	9

class CRestoreObj : public IRestoreObj {
protected:
	virtual void	undo(){}
	virtual void	redo(){}
	virtual void	undo_begin(){}
	virtual void	undo_end(){}
	virtual void	redo_begin(){}
	virtual void	redo_end(){}
	virtual bool	is_undo_enabled(){ return true; }
	virtual bool	is_redo_enabled(){ return true; }
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 bool	invoke( uint32_t, uint32_t	uMsg ){
		bool	result = true;
		if( uMsg == UNDOREDO_DETACHED ){
			release();
		}
		else if( uMsg == UNDOREDO_ATTACHED ){
			add_ref();
		}
		else if( uMsg == UNDOREDO_UNDO ){
			undo();
		}
		else if( uMsg == UNDOREDO_REDO ){
			redo();
		}
		else if( uMsg == UNDOREDO_UNDO_BEGIN ){
			undo_begin();
		}
		else if( uMsg == UNDOREDO_UNDO_END ){
			undo_end();
		}
		else if( uMsg == UNDOREDO_REDO_BEGIN ){
			redo_begin();
		}
		else if( uMsg == UNDOREDO_REDO_END ){
			redo_end();
		}
		else if( uMsg == UNDOREDO_UNDO_ENABLED ){
			result = is_undo_enabled();
		}
		else if( uMsg == UNDOREDO_REDO_ENABLED ){
			result = is_redo_enabled();
		}
		return	result;
	}
	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)
#define	INVALID_CHAIN_ID	(( uint32_t )-1)
#define	MAXIMUM_CHAIN_LEVEL	(( uint32_t )-1)

class cundoredoitem {
	uint32_t	m_chain_id;
	uint32_t	m_chain_level;
	uint32_t	m_id;
	IRestoreObj*	m_pRestoreObj;
		
	bool	dispatch( uint32_t	uMsg );
public:
	void	detach();
	bool	attach( uint32_t	chain_id, uint32_t	chain_level, uint32_t	id, IRestoreObj*	pRestoreObj );
	bool	get_object( IRestoreObj**	ppRestoreObj ) const;
	bool	undo_begin();
	bool	undo_end();
	bool	undo();
	bool	redo_begin();
	bool	redo_end();
	bool	redo();
	bool	is_undo_enabled();
	bool	is_redo_enabled();
		
	uint32_t	chain_id() const;
	uint32_t	chain_level() const;
	uint32_t	id() const;
		
	bool	operator==( uint32_t	id ) const;
	cundoredoitem&	operator=( const cundoredoitem&	rhs );
		
	cundoredoitem();
	cundoredoitem( uint32_t	chain_id, uint32_t	chain_level, uint32_t	id, IRestoreObj*	pRestoreObj );
	cundoredoitem( const cundoredoitem&	rhs );
	~cundoredoitem();
};
inline uint32_t	cundoredoitem::chain_id() const {
	return	m_chain_id;
}
inline uint32_t	cundoredoitem::chain_level() const {
	return	m_chain_level;
}
inline uint32_t	cundoredoitem::id() const {
	return	m_id;
}
inline bool	cundoredoitem::operator==( uint32_t	id ) const {
	return	( m_id == id );
}
inline cundoredoitem&	cundoredoitem::operator=( const cundoredoitem&	rhs ){
	if( this != &rhs ){
		m_chain_id = rhs.m_chain_id;
		m_chain_level = rhs.m_chain_level;
		m_id = rhs.m_id;
		if( m_pRestoreObj ){
			m_pRestoreObj->release();
		}
		m_pRestoreObj = rhs.m_pRestoreObj;
		if( m_pRestoreObj ){
			m_pRestoreObj->add_ref();
		}
	}
	return	*this;
}
inline cundoredoitem::cundoredoitem():
m_chain_id(INVALID_CHAIN_ID),
m_chain_level(0),
m_id(INVALID_COMMAND_ID),
m_pRestoreObj(NULL)
{
	if( m_pRestoreObj ){
		m_pRestoreObj->add_ref();
	}
}
inline cundoredoitem::cundoredoitem( uint32_t	chain_id, uint32_t	chain_level, uint32_t	id, IRestoreObj*	pRestoreObj ):
m_chain_id(chain_id),
m_chain_level(chain_level),
m_id(id),
m_pRestoreObj(pRestoreObj)
{
	if( m_pRestoreObj ){
		m_pRestoreObj->add_ref();
	}
}
inline cundoredoitem::cundoredoitem( const cundoredoitem&	rhs ):
m_chain_id(rhs.m_chain_id),
m_chain_level(rhs.m_chain_level),
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 ){
		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_chain_id = INVALID_CHAIN_ID;
	m_chain_level = 0;
	m_id = INVALID_COMMAND_ID;
}
inline bool	cundoredoitem::attach( uint32_t	chain_id, uint32_t	chain_level, uint32_t	id, IRestoreObj*	pRestoreObj ){
	bool	result = true;
	if(( m_chain_id != chain_id )
	 ||( m_chain_level != chain_level )
	 ||( m_id != id )
	 ||( m_pRestoreObj != pRestoreObj )
	){
		detach();
		if( pRestoreObj ){
			m_chain_id = chain_id;
			m_chain_level = chain_level;
			m_id = id;
			pRestoreObj->add_ref();
			m_pRestoreObj = pRestoreObj;
			result = dispatch( UNDOREDO_ATTACHED );
			if( !result ){
				m_pRestoreObj->release();
				m_pRestoreObj = NULL;
				m_chain_id = INVALID_CHAIN_ID;
				m_chain_level = 0;
				m_id = INVALID_COMMAND_ID;
			}
		}
	}
	return	result;
}
inline bool	cundoredoitem::get_object( IRestoreObj**	ppRestoreObj ) const {
	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_begin(){
	return	dispatch( UNDOREDO_UNDO_BEGIN );
}
inline bool	cundoredoitem::undo_end(){
	return	dispatch( UNDOREDO_UNDO_END );
}
inline bool	cundoredoitem::undo(){
	return	dispatch( UNDOREDO_UNDO );
}
inline bool	cundoredoitem::redo_begin(){
	return	dispatch( UNDOREDO_REDO_BEGIN );
}
inline bool	cundoredoitem::redo_end(){
	return	dispatch( UNDOREDO_REDO_END );
}
inline bool	cundoredoitem::redo(){
	return	dispatch( UNDOREDO_REDO );
}
inline bool	cundoredoitem::is_undo_enabled(){
	return	dispatch( UNDOREDO_UNDO_ENABLED );
}
inline bool	cundoredoitem::is_redo_enabled(){
	return	dispatch( UNDOREDO_REDO_ENABLED );
}

Undo チェイン管理クラスの実装

class cundoredochain : public std::vector<uint32_t> {
	uint32_t	m_id;
	std::string	m_name;
public:
	uint32_t	id() const {
		return	m_id;
	}
	const std::string&	name() const {
		return	m_name;
	}
	void	attach( uint32_t	id, const std::string&	name ){
		m_id = id;
		m_name = name;
		clear();
	}
	cundoredochain&	operator=( const cundoredochain&	rhs ){
		if( this != &rhs ){
			m_id = rhs.m_id;
			m_name = rhs.m_name;
			std::vector<uint32_t>::operator=( rhs );
		}
		return	*this;
	}
	cundoredochain():
	std::vector<uint32_t>(),
	m_id(INVALID_CHAIN_ID),
	m_name()
	{
	}
	cundoredochain( uint32_t	id, const std::string&	name ):
	std::vector<uint32_t>(),
	m_id(id),
	m_name(name)
	{
	}
	cundoredochain( const cundoredochain&	rhs ):
	std::vector<uint32_t>(rhs),
	m_id(rhs.m_id),
	m_name(rhs.m_name)
	{
	}
};

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

class cundoredo {
	std::deque<cundoredoitem>	m_redo;
	std::deque<cundoredoitem>	m_undo;
	std::map<uint32_t,cundoredochain>	m_chains;
	uint32_t	m_chain_id;
	uint32_t	m_chain_level;
	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 ) const;
	bool	chain_begin( const std::string&	chain_name );
	bool	chain_end();
	bool	chain_cancel();
	bool	undo();
	bool	redo();
	bool	is_undo_available() const;
	bool	is_redo_available() const;
	size_t	get_undo_chain_count() const;
	size_t	get_redo_chain_count() const;
	std::vector<uint32_t>	get_undo_chains() const;
	std::vector<uint32_t>	get_redo_chains() const;
	std::string	get_chain_name( uint32_t	chain_id ) const;
	bool	is_undo_enabled( uint32_t	chain_id ) const;
	bool	is_redo_enabled( uint32_t	chain_id ) const;
	
	cundoredo();
	~cundoredo();
private:
	cundoredo( const cundoredo& );
	cundoredo&	operator=( const cundoredo& );
};
cundoredo::cundoredo():
m_redo(),
m_undo(),
m_chains(),
m_chain_id(INVALID_CHAIN_ID),
m_chain_level(0),
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;
	}
	else if( m_chain_id == INVALID_CHAIN_ID ){
		result = false;
	}
	if( result ){
		cundoredoitem	item;
		result = item.attach( m_chain_id, m_chain_level, ++m_id, pRestoreObj );
		if( result ){
			m_chains[m_chain_id].push_back( m_id );
			m_undo.push_back( item );
			for( std::deque<cundoredoitem>::iterator	p = m_redo.begin(); p != m_redo.end(); ++p ){
				m_chains.erase( p->chain_id());
				p->detach();
			}
			m_redo.clear();
			result = m_undo.rbegin()->redo();
			if( pid ){
				*pid = m_id;
			}
		}
	}
	return	result;
}
bool	cundoredo::get_object( uint32_t	id, IRestoreObj**	ppRestoreObj ) const {
	if( ppRestoreObj ){
		*ppRestoreObj = NULL;
	}
	bool	result = true;
	if( !ppRestoreObj ){
		result = false;
	}
	if( result ){
		std::deque<cundoredoitem>::const_iterator	p = std::find( m_undo.begin(), m_undo.end(), id );
		if( p == m_undo.end()){
			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_chains.clear();
		m_chain_id = INVALID_CHAIN_ID;
		m_chain_level = 0;
		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()){
			uint32_t	chain_id = p->chain_id();
			p->detach();
			m_undo.erase( p );
			m_chains[chain_id].push_back( id );
			result = true;
		}
		else{
			p = std::find( m_redo.begin(), m_redo.end(), id );
			if( p != m_redo.end()){
				uint32_t	chain_id = p->chain_id();
				p->detach();
				m_redo.erase( p );
				m_chains[chain_id].push_back( id );
				result = true;
			}
		}
	}
	return	result;
}
bool	cundoredo::is_undo_available() const {
	return	!m_undo.empty();
}
bool	cundoredo::is_redo_available() const {
	return	!m_redo.empty();
}
bool	cundoredo::undo(){
	if( !m_undo.empty()){
		std::deque<cundoredoitem>	undo;
		do{
			undo.push_back( m_undo.back());
			m_undo.pop_back();
		}while( !m_undo.empty() &&( m_undo.rbegin()->chain_id() == undo.rbegin()->chain_id()));
		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 ){
			if( p == undo.begin()){
				p->undo_begin();
			}
			p->undo();
			std::deque<cundoredoitem>::iterator	q = p;
			if( ++q == undo.end()){
				p->undo_end();
			}
		}
	}
	return	true;
}
bool	cundoredo::redo(){
	if( !m_redo.empty()){
		std::deque<cundoredoitem>	redo;
		do{
			redo.push_back( m_redo.back());
			m_redo.pop_back();
		}while( !m_redo.empty() &&( m_redo.rbegin()->chain_id() == redo.rbegin()->chain_id()));
		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 ){
			if( p == redo.rbegin()){
				p->redo_begin();
			}
			p->redo();
			std::deque<cundoredoitem>::reverse_iterator	q = p;
			if( ++q == redo.rend()){
				p->redo_end();
			}
		}
	}
	return	true;
}
bool	cundoredo::chain_begin( const std::string&	chain_name ){
	bool	result = true;
	if( !m_chain_level ){
		if( m_chain_id + 1 == INVALID_CHAIN_ID ){
			result = false;
		}
		if( result ){
			++m_chain_level;
			++m_chain_id;
			m_chains[m_chain_id].attach( m_chain_id, chain_name );
		}
	}
	else{
		if( m_chain_level + 1 >= MAXIMUM_CHAIN_LEVEL ){
			result = false;
		}
		if( result ){
			++m_chain_level;
		}
	}
	return	result;
}
bool	cundoredo::chain_end(){
	bool	result = true;
	if( !m_chain_level ){
		result = false;
	}
	if( result ){
		--m_chain_level;
	}
	return	result;
}
bool	cundoredo::chain_cancel(){
	bool	result = true;
	if( !m_chain_level ){
		result = false;
	}
	if( result ){
		while( !m_undo.empty()){
			std::deque<cundoredoitem>::reverse_iterator	p = m_undo.rbegin();
			if(( p->chain_id() != m_chain_id )||( p->chain_level() != m_chain_level )){
				break;
			}
			p->undo();
			p->detach();
			m_undo.pop_back();
		}
		m_chains.erase( m_chain_id );
		--m_chain_level;
	}
	return	result;
}
size_t	cundoredo::get_undo_chain_count() const {
	size_t	chains = 0;
	uint32_t	chain_id_prev = INVALID_CHAIN_ID;
	for( std::deque<cundoredoitem>::const_iterator	p = m_undo.begin(); p != m_undo.end(); ++p ){
		if( !chains ||( chain_id_prev != p->chain_id())){
			chain_id_prev =  p->chain_id();
			++chains;
		}
	}
	return	chains;
}
std::vector<uint32_t>	cundoredo::get_undo_chains() const {
	std::vector<std::pair<uint32_t,std::string> >	chains;
	chains.reserve( m_undo.size());
	for( std::deque<cundoredoitem>::const_reverse_iterator	p = m_undo.rbegin(); p != m_undo.rend(); ++p ){
		if( chains.empty() ||( chains.rbegin()->first != p->chain_id())){
			chains.push_back( std::make_pair( p->chain_id(), m_chains.find( p->chain_id())->second.name()));
		}
	}
	std::vector<uint32_t>	result;
	result.reserve( chains.size());
	for( std::vector<std::pair<uint32_t,std::string> >::iterator	p = chains.begin(); p != chains.end(); ++p ){
		result.push_back( p->first );
	}
	return	result;
}
size_t	cundoredo::get_redo_chain_count() const {
	size_t	chains = 0;
	uint32_t	chain_id_prev = INVALID_CHAIN_ID;
	for( std::deque<cundoredoitem>::const_iterator	p = m_redo.begin(); p != m_redo.end(); ++p ){
		if( !chains ||( chain_id_prev != p->chain_id())){
			chain_id_prev =  p->chain_id();
			++chains;
		}
	}
	return	chains;
}
std::vector<uint32_t>	cundoredo::get_redo_chains() const {
	std::vector<std::pair<uint32_t,std::string> >	chains;
	chains.reserve( m_redo.size());
	for( std::deque<cundoredoitem>::const_reverse_iterator	p = m_redo.rbegin(); p != m_redo.rend(); ++p ){
		if( chains.empty() ||( chains.rbegin()->first != p->chain_id())){
			chains.push_back( std::make_pair( p->chain_id(), m_chains.find( p->chain_id())->second.name()));
		}
	}
	std::vector<uint32_t>	result;
	result.reserve( chains.size());
	for( std::vector<std::pair<uint32_t,std::string> >::iterator	p = chains.begin(); p != chains.end(); ++p ){
		result.push_back( p->first );
	}
	return	result;
}
std::string	cundoredo::get_chain_name( uint32_t	chain_id ) const {
	std::string	name;
	std::map<uint32_t,cundoredochain>::const_iterator	p = m_chains.find( chain_id );
	if( p != m_chains.end()){
		name = p->second.name();
	}
	return	name;
}
bool	cundoredo::is_undo_enabled( uint32_t	chain_id ) const {
	bool	result = true;
	std::map<uint32_t,cundoredochain>::const_iterator	p = m_chains.find( chain_id );
	if( p != m_chains.end()){
		for( cundoredochain::const_iterator	i = p->second.begin(); i != p->second.end(); ++i ){
			IRestoreObj*	pRestoreObj = NULL;
			get_object( *i, &pRestoreObj );
			if( pRestoreObj ){
				result = pRestoreObj->invoke( *i, UNDOREDO_UNDO_ENABLED );
				pRestoreObj->release();
				if( !result ){
					break;
				}
			}
		}
	}
	return	result;
}
bool	cundoredo::is_redo_enabled( uint32_t	chain_id ) const {
	bool	result = true;
	std::map<uint32_t,cundoredochain>::const_iterator	p = m_chains.find( chain_id );
	if( p != m_chains.end()){
		for( cundoredochain::const_iterator	i = p->second.begin(); i != p->second.end(); ++i ){
			IRestoreObj*	pRestoreObj = NULL;
			get_object( *i, &pRestoreObj );
			if( pRestoreObj ){
				result = pRestoreObj->invoke( *i, UNDOREDO_REDO_ENABLED );
				pRestoreObj->release();
				if( !result ){
					break;
				}
			}
		}
	}
	return	result;
}

テストコード

inline std::string	GetCurrentDirectoryA(){
	std::string	s;
	uint32_t	cch = GetCurrentDirectoryA( 0, NULL );
	if( cch ){
		s.resize( cch+1 );
		GetCurrentDirectoryA( cch, &s[0] );
		s.erase( std::find( s.begin(), s.end(), 0 ), s.end());
	}
	return	s;
}
class CRestoreObj_chdir : public CRestoreObj {
	std::string	m_old;
	std::string	m_new;
	
	void	chdir( const char* prefix, const std::string&	s ){
		if( GetFileAttributes( s.c_str()) != INVALID_FILE_ATTRIBUTES ){
			std::string	prev = GetCurrentDirectory();
			SetCurrentDirectory( s.c_str());
			debug_printf( "%s: \"%s\"\"%s\"\n", prefix, prev.c_str(), s.c_str());
		}
		else{
			debug_printf( "\t(disabled)\n" );
		}
	}
public:
	void	undo(){
		chdir( "undo", m_old );
	}
	void	redo(){
		chdir( "redo", m_new );
	}
	void	undo_begin(){
		debug_printf( "undo:{\n" );
	}
	void	undo_end(){
		debug_printf( "undo:}\n" );
	}
	void	redo_begin(){
		debug_printf( "redo:{\n" );
	}
	void	redo_end(){
		debug_printf( "redo:}\n" );
	}
	bool	is_undo_enabled(){
		return	( GetFileAttributes( m_new.c_str()) != INVALID_FILE_ATTRIBUTES );
	}
	bool	is_redo_enabled(){
		return	( GetFileAttributes( m_new.c_str()) != INVALID_FILE_ATTRIBUTES );
	}
	
	explicit CRestoreObj_chdir( const std::string&	new_ ):
	CRestoreObj(),
	m_old(GetCurrentDirectory()),
	m_new(new_)
	{
	}
	virtual ~CRestoreObj_chdir(){
	}
private:
	CRestoreObj_chdir();
	CRestoreObj_chdir( const CRestoreObj_chdir& );
	CRestoreObj_chdir&	operator=( const CRestoreObj_chdir& );
};
inline std::string	GetFullPathNameA( LPCSTR	pszFileName, SIZE_T*	pnFilePart = NULL ){
	size_t	nFilePart = 0;
	std::string	s;
	uint32_t	cch = GetFullPathNameA( pszFileName, 0, NULL, NULL );
	if( cch ){
		s.resize( cch );
		LPSTR	pFilePart = &s[0];
		GetFullPathNameA( pszFileName, cch, &s[0], &pFilePart );
		s.erase( std::find( s.begin(), s.end(), 0 ), s.end());
		if( !s.empty()){
			nFilePart = ( size_t )( pFilePart - &s[0] );
		}
	}
	if( pnFilePart ){
		*pnFilePart = nFilePart;
	}
	return	s;
}
inline void	chdir( cundoredo&	undoredo, const std::string&	caption, const std::string&	dir, uint32_t*	pid = NULL ){
	IRestoreObj*	p = new CRestoreObj_chdir( dir );
	undoredo.chain_begin( caption + dir );
	undoredo.add_object( p, pid );
	undoredo.chain_end();
	p->release();
}
void undo( cundoredo&	undoredo ){
	std::vector<uint32_t>	chains = undoredo.get_undo_chains();
	if( !chains.empty()){
		bool	fDone = false;
		std::vector<uint32_t>::iterator	p = chains.begin();
		while( ++p != chains.end()){
			undoredo.undo();
			if( undoredo.is_undo_enabled( *p )){
				fDone = true;
				break;
			}
		}
		if( !fDone ){
			undoredo.undo();
		}
	}
}
void redo( cundoredo&	undoredo ){
	std::vector<uint32_t>	chains = undoredo.get_redo_chains();
	for( std::vector<uint32_t>::iterator	p = chains.begin(); p != chains.end(); ++p ){
		undoredo.redo();
		if( undoredo.is_undo_enabled( *p )){
			break;
		}
	}
}
int	main(){
	std::vector<std::string>	sss;
	sss.push_back( GetFullPathName("aaaa"));
	sss.push_back( GetFullPathName("bbbb"));
	sss.push_back( GetFullPathName("cccc"));
	sss.push_back( GetCurrentDirectory());
	CreateDirectory( sss[0].c_str(), NULL );
	CreateDirectory( sss[1].c_str(), NULL );
	CreateDirectory( sss[2].c_str(), NULL );
	
	cundoredo	undoredo;
	chdir( undoredo, "[0]", sss[0] );
	chdir( undoredo, "[1]", sss[1] );
	chdir( undoredo, "[2]", sss[2] );
	chdir( undoredo, "[3]", sss[3] );
	output_chains( undoredo );
	
	RemoveDirectory( sss[1].c_str());
	debug_printf( "\nRemoveDirectory( \"%s\" );\n\n", sss[1].c_str());

	undo( undoredo );
	output_chains( undoredo );
	undo( undoredo );
	output_chains( undoredo );
	undo( undoredo );
	output_chains( undoredo );
	redo( undoredo );
	output_chains( undoredo );
	redo( undoredo );
	output_chains( undoredo );
	return	0;
}

出力

redo: "D:\undo_06_disable" → "D:\undo_06_disable\aaaa"
redo: "D:\undo_06_disable\aaaa" → "D:\undo_06_disable\bbbb"
redo: "D:\undo_06_disable\bbbb" → "D:\undo_06_disable\cccc"
redo: "D:\undo_06_disable\cccc" → "D:\undo_06_disable"
{
    [0]D:\undo_06_disable\aaaa
    [1]D:\undo_06_disable\bbbb
    [2]D:\undo_06_disable\cccc
    [3]D:\undo_06_disable
---
}

RemoveDirectory( "D:\undo_06_disable\bbbb" );

undo:{
undo: "D:\undo_06_disable" → "D:\undo_06_disable\cccc"
undo:}
{
    [0]D:\undo_06_disable\aaaa
    [1]D:\undo_06_disable\bbbb (disabled)
    [2]D:\undo_06_disable\cccc
---
    [3]D:\undo_06_disable
}
undo:{
	(disabled)
undo:}
undo:{
undo: "D:\undo_06_disable\cccc" → "D:\undo_06_disable\aaaa"
undo:}
{
    [0]D:\undo_06_disable\aaaa
---
    [1]D:\undo_06_disable\bbbb
    [2]D:\undo_06_disable\cccc
    [3]D:\undo_06_disable
}
undo:{
undo: "D:\undo_06_disable\aaaa" → "D:\undo_06_disable"
undo:}
{
---
    [0]D:\undo_06_disable\aaaa
    [1]D:\undo_06_disable\bbbb
    [2]D:\undo_06_disable\cccc
    [3]D:\undo_06_disable
}
redo:{
redo: "D:\undo_06_disable" → "D:\undo_06_disable\aaaa"
redo:}
{
    [0]D:\undo_06_disable\aaaa
---
    [1]D:\undo_06_disable\bbbb
    [2]D:\undo_06_disable\cccc
    [3]D:\undo_06_disable
}
redo:{
	(disabled)
redo:}
redo:{
redo: "D:\undo_06_disable\aaaa" → "D:\undo_06_disable\cccc"
redo:}
{
    [0]D:\undo_06_disable\aaaa
    [1]D:\undo_06_disable\bbbb (disabled)
    [2]D:\undo_06_disable\cccc
---
    [3]D:\undo_06_disable
}