Jump to content
  • Advertisement
Sign in to follow this  
CTar

Unhandled exception, can't find bug

This topic is 4886 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

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?

Share this post


Link to post
Share on other sites
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
};


Share this post


Link to post
Share on other sites
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).



Share this post


Link to post
Share on other sites
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> >.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
No, you instantiate it with a pair<Key, Type>. Remember std::pair is just another kind of type.

Share this post


Link to post
Share on other sites
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]

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!