Unhandled exception, can't find bug

Started by
7 comments, last by CTar 18 years, 8 months ago
I have just (re)started working on an engine. I'm currently writting the logging system, but it has an error I cant find, so I thought I'ld post the problem here. My logging system currently consists of an interface, ILog and a factory class, LogFactory. LogFactory inherits from FactoryBase<ILog> and Singleton<LogFactory>, I haven't used multiple inheritance much before so I don't know if that is the problem. My singleton class, looks like this:

template<typename T>
class Singleton
{
public:
	~Singleton();
	static T* Instance();
protected:
	Singleton();
	Singleton(const Singleton& p_Copy);
	Singleton& operator=(const Singleton<T>& p_RHS);
};

// Definition
template<typename T>
Singleton<T>::Singleton()
{
}
template<typename T>
Singleton<T>::~Singleton()
{
}
template<typename T>
Singleton<T>::Singleton(const Singleton&)
{
}
template<typename T>
Singleton<T>& Singleton<T>::operator=(const Singleton<T>&)
{
	assert(!"Don't call this function");
	// Shouldn't be called, therefore output doesn't matter.
	return (*this);
}
template<typename T>
T* Singleton<T>::Instance()
{
	static T ObjectInstance;
	return &ObjectInstance;
}

My ILog class looks like this:

// M3DBASE_EXPORT is a #define for either __declspec(dllexport) or
// __declspec(dllimport)
class M3DBASE_EXPORT ILog
{
public:
	ILog();
	virtual ~ILog();

	virtual void Write(
		const String&	p_Message,
		const String&	p_File,
		UInt32		p_Line) = 0;
	virtual void Write(
		const String&	p_Message) = 0;
	virtual ILog* Clone() = 0;
};

My FactoryBase class looks like this:

template<typename TBase>
class FactoryBase
{
public:
	FactoryBase();
	virtual ~FactoryBase();

	template<typename T>
	void RegisterType(const String& p_Identifier)
	{
		assert(m_RegisteredTypes.end() == m_RegisteredTypes.find(p_Identifier));

		m_RegisteredTypes[p_Identifier] = new T;

		return;
	}

	TBase* Create(const String&);

private:
	typedef std::map<String,TBase*> MapType;
	typedef std::list<TBase*> ListType;

	MapType		m_RegisteredTypes;
	ListType	m_CreatedObjects;
};

// Definition
template<typename TBase>
FactoryBase<TBase>::FactoryBase()
{
	return;
}
template<typename TBase>
FactoryBase<TBase>::~FactoryBase()
{
	// Destroy the prototypes
	for(MapType::iterator iter = m_RegisteredTypes.begin();
		m_RegisteredTypes.end() != iter;
		++iter)
	{
		delete iter->second;
	}
	// Destroy the created objects
	for(ListType::iterator iter = m_CreatedObjects.begin();
		m_CreatedObjects.end() != iter;
		++iter)
	{
		delete (*iter);
	}
	return;
}
template<typename TBase>
TBase* FactoryBase<TBase>::Create(const String& p_Identifier)
{
	assert(m_RegisteredTypes.end() != m_RegisteredTypes.find(p_Identifier));
	
	TBase* Return = m_RegisteredTypes[p_Identifier]->Clone();

	m_CreatedObjects.push_back(Return);

	return Return;
}

My LogFactory class looks like this:

class M3DBASE_EXPORT LogFactory : public Singleton< LogFactory >,public FactoryBase<ILog>
{
public:
	LogFactory(){}
	virtual ~LogFactory(){}
};

I written the following test:

int main()
{
	try
	{
		LogFactory::Instance()->RegisterType<ConsoleLog>( _T("ConsoleLog") );
		return 0;
	}
	catch(...)
	{
		std::cout << "Exception caught." << std::endl;
		std::cin.get();
		return 1;
	}
}

The problem is that AFTER main have returned 0 an exception is thrown, I know that my singleton generates a: "static LogFactory ObjectInstance;" And the destructor of this object is called after main, the weird thing is that first after both LogFactory and FactoryBase's destructor have exitted the exception is thrown. Looking at the callstack I can see that it chrashes in std::map's destructor, when std::map is trying to delete a memory address, that memory address is the address of the ConsoleLog which were registered on the second line of main. Actually I thought I'ld have to delete this myself, but for some reason std::map tries to delete it and throws an exception in _CrtIsValidHeapPointer. I thought it threw the exception because I also tried to delete that memory, but even though I comment out all my delete statements it fails in that delete call. I also tried to ".clear()" the std::map before exitting the destructor, but for some reason it still knows the memory address of the ConsoleLog. I believe the error have something to do with using a DLL, because I tried to copy-paste some of the code to a single main.cpp file and here it works fine. The source which works fine is this:

#include <map>
#include <string>
#include <iostream>
#include <list>
#include <tchar.h>

typedef unsigned int		UInt32;

#ifdef UNICODE
typedef std::wstring		String;
#else
typedef std::string		String;
#endif

template<typename T>
class Singleton
{
public:
	virtual ~Singleton(){}

	static T* Instance()
	{
		static T ObjectInstance;

		return &ObjectInstance;
	}
protected:
	Singleton(){}
	Singleton(const Singleton& p_Copy){}
	Singleton& operator=(const Singleton<T>& p_RHS)
	{
		return (*this);
	}
};

class ILog
{
public:
	ILog(){}
	virtual ~ILog(){}

	virtual void Write(
		const String&	p_Message,
		const String&	p_File,
		UInt32			p_Line) = 0;
	virtual void Write(
		const String&	p_Message) = 0;
	virtual ILog* Clone() = 0;
};

template<typename TBase>
class FactoryBase
{
public:

	FactoryBase(){}
	virtual ~FactoryBase()
	{
		// Destroy the prototypes
		for(MapType::iterator iter = m_RegisteredTypes.begin();
			m_RegisteredTypes.end() != iter;
			++iter)
		{
			delete iter->second;
		}
		m_RegisteredTypes.clear();
		// Destroy the created objects
		for(ListType::iterator iter = m_CreatedObjects.begin();
			m_CreatedObjects.end() != iter;
			++iter)
		{
			delete *iter;
		}
		m_CreatedObjects.clear();
		return;
	}

	template<typename T>
	void RegisterType(const String& p_Identifier)
	{		
		m_RegisteredTypes[p_Identifier] = new T;
	}

	TBase* Create(const String&)
	{
		m_RegisteredTypes[p_Identifier]->Clone();

		m_CreatedObjects.push_back(Return);

		return Return;
	}

private:
	typedef std::map<String,TBase*> MapType;
	typedef std::list<TBase*> ListType;

	MapType		m_RegisteredTypes;
	ListType	m_CreatedObjects;
};

class LogFactory : public Singleton< LogFactory >,public FactoryBase<ILog>
{
public:
	LogFactory(){}
	virtual ~LogFactory(){}
};

class ConsoleLog : public ILog
{
	typedef std::list<ILog*> ListType;
	ListType m_ClonedLogs;
public:
	ConsoleLog(){}
	virtual ~ConsoleLog()
	{
		for( ListType::iterator iter = m_ClonedLogs.begin();
			m_ClonedLogs.end() != iter;
			++iter)
		{
			delete (*iter);
		}
	}

	virtual void Write(
		const String&	p_Message,
		const String&	p_File,
		UInt32			p_Line)
	{
		std::cout << "Message: " << p_Message.c_str() << "[" << p_File.c_str() << ", " << p_Line << "]" << std::endl;
	}
	virtual void Write(
		const String&	p_Message)
	{
		std::cout << "Message: " << p_Message.c_str() << std::endl;
	}
	virtual ILog* Clone()
	{
		ILog* Return = new ConsoleLog();
		m_ClonedLogs.push_back(Return);
		return Return;
	}
};

int main()
{
	LogFactory::Instance()->RegisterType<ConsoleLog>( _T("ConsoleLog") );
}

Does anyone have an idea of what the bug is?
Advertisement
Quote:Original post by CTar
I believe the error have something to do with using a DLL, because I tried to copy-paste some of the code to a single main.cpp file and here it works fine. The source which works fine is this:


Yes, this could well be the problem. Dlls use a different heap to their client executables, so memory allocated in one cannot be freed by the other.

STL does allow you to assign a custom allocator, which can use the win32 memory allocation commands rather than relying on new().

I forget who wrote this (it wasn't me), but here is an example of a custom allocator that appears to work:

template<class _Ty>class win32_allocator{public:	typedef _SIZT size_type;	typedef _PDFT difference_type;	typedef _Ty _FARQ *pointer;	typedef const _Ty _FARQ *const_pointer;	typedef _Ty _FARQ& reference;	typedef const _Ty _FARQ& const_reference;	typedef _Ty value_type;	win32_allocator()	{	}	template <class U> 	win32_allocator(win32_allocator<U> const&)	{	}	~win32_allocator()	{	}	pointer address(reference _X) const	{		return (&_X);	}	const_pointer address(const_reference _X) const	{		return (&_X);	}	template <class U>	bool operator == (win32_allocator<U> const&)	{		return true;	}	pointer allocate(size_type _N, const void *)	{		if(_N < 0)		{			_N = 0;		}		// Allocate _N instances from Win32 Process Heap		return (pointer)HeapAlloc(GetProcessHeap(), HEAP_GENERATE_EXCEPTIONS, _N * sizeof(_Ty));	}	char _FARQ *_Charalloc(size_type _N)	{		if(_N < 0)		{			_N = 0;		}		// Allocate _N *bytes* from Win32 Process Heap		return (char*)HeapAlloc(GetProcessHeap(), HEAP_GENERATE_EXCEPTIONS, _N);	}	void deallocate(void _FARQ *_P, size_type)	{		if(_P)		{			// Call destructor			//destroy((_Ty*)_P);			// Deallocate from Win32 Process Heap			HeapFree(GetProcessHeap(), 0, _P);		}	}	void construct(pointer _P, const _Ty& _V)	{		std::_Construct(_P, _V);	}	void destroy(pointer _P)	{			std::_Destroy(_P);	}	_SIZT max_size() const	{		_SIZT _N = (_SIZT)(-1) / sizeof (_Ty);		return (0 < _N ? _N : 1);	}#if _MSC_VER >= 1300 	template<class Other>	struct rebind 	{		typedef win32_allocator<Other> other;	};#endif};
Quote:Original post by Sandman
Quote:Original post by CTar
I believe the error have something to do with using a DLL, because I tried to copy-paste some of the code to a single main.cpp file and here it works fine. The source which works fine is this:


Yes, this could well be the problem. Dlls use a different heap to their client executables, so memory allocated in one cannot be freed by the other.

STL does allow you to assign a custom allocator, which can use the win32 memory allocation commands rather than relying on new().

I forget who wrote this (it wasn't me), but here is an example of a custom allocator that appears to work:

*** Source Snippet Removed ***


Thanks, I'll try to see if I can get it to work, but needs another allocator first because it is for a std::map whose allocator takes two template arguments(one for the key type and one for the actual type).



Quote:Original post by CTar
Thanks, I'll try to see if I can get it to work, but needs another allocator first because it is for a std::map whose allocator takes two template arguments(one for the key type and one for the actual type).


Only if you are using some sort of alternate universe std::map. std::map only takes one allocator, which defaults to allocator<pair <const Key, Type> >.
Quote:Original post by SiCrane
Quote:Original post by CTar
Thanks, I'll try to see if I can get it to work, but needs another allocator first because it is for a std::map whose allocator takes two template arguments(one for the key type and one for the actual type).


Only if you are using some sort of alternate universe std::map. std::map only takes one allocator, which defaults to allocator<pair <const Key, Type> >.


That was what I meant, the win32_allocator takes one template argument(Ty) while std::map's allocater takes a pair<KTy,Ty>, so I cant use the win32_allocator with std::map.
No, you instantiate it with a pair<Key, Type>. Remember std::pair is just another kind of type.
Quote:Original post by SiCrane
No, you instantiate it with a pair<Key, Type>. Remember std::pair is just another kind of type.


Thanks, I dont know how I could have misunderstood that. I got it to work without any errors now, but I'm trying to write as cross-platform code as possible (at least Win32 and Linux), so does anyone know if it is possible to do a similar allocation in Linux?

[Edited by - CTar on October 16, 2005 2:57:45 PM]
Shared objects in linux work differently that DLLs do. I'm relatively certain (call it 85%) that you won't need to do these kinds of contortions in linux.
Quote:Original post by SiCrane
Shared objects in linux work differently that DLLs do. I'm relatively certain (call it 85%) that you won't need to do these kinds of contortions in linux.


Ok, thanks, I'll find out when I try to compile it on Linux and if the same problem arises then there is probably also a solution.

This topic is closed to new replies.

Advertisement