My first shot at c++ exceptions

Started by
10 comments, last by the_edd 15 years, 9 months ago
Ok I have been using return codes for a very long time. I didnt initialized objects in constructors because they dont have return codes. Instead I created a method like bool create(parameters) to create the object. And I destructed the object in the destructor. Ok here is an example of what i have at the moment.

bool DAGameApp::initApplication(HINSTANCE hInstance)
{
	
	
	
	if(DialogBox(hInstance,MAKEINTRESOURCE(IDD_OPTIONS),NULL,(DLGPROC)dlgOptionsProc)==0)
		return false;
	
	if(!registerWindowClass("DAGameClass",hInstance,DAGameWindow::WndProc,loadIcon(IDI_APPLICATION),STWBACK_BLACK))
		return false;
	
	m_MainWnd.reset(new DAGameWindow);
	if(!m_MainWnd)
		return false;
	
	//Set creation params
	m_MainWnd->setWidth(g_InitInfo.iWidth);
	m_MainWnd->setHeight(g_InitInfo.iHeight);
	m_MainWnd->setFullScreen(g_InitInfo.bFullscreen);
	m_MainWnd->setBits(g_InitInfo.iBits);
	
	DWORD dwStyle = WS_VISIBLE;
	if(g_InitInfo.bFullscreen)
		dwStyle|=FULLSCREEN;
	else
		dwStyle|=WINDOWED;
	
	//Create the game window
	if(!m_MainWnd->create("DAGameClass","Dark Age v0.3",NULL,NULL,dwStyle))
		return false;
		
	//if the window creation succeded create the game objects
	if(!m_MainWnd->createGameObjects())
		return false;

	//Hide the cursor if necessary
	if(g_InitInfo.bFullscreen)
		ShowCursor(FALSE);
	return true;


}


m_MainWnd is a boost::shared_ptr.As you can see I have lots of ugly if statements for each method.I think i can get rid of these if I switch to exceptions.So I derived a class from std::runtime_error:


namespace STE
{
	class STException : public std::runtime_error
	{
	public:
		STException(const std::string& error):std::runtime_error(error){}

	private:
		

	};
}


And i threw from my library functions when they fail here is the registerWindowClass method from above :

void WndApp::registerWindowClass(char *szClass,HINSTANCE hInstance,WNDPROC WndProc,HICON hIcon,int iBackground)
    {
	    m_hInstance = hInstance;
	    WNDCLASSEX wndClass;
    	
	    wndClass.cbSize			= sizeof(WNDCLASSEX);
	    wndClass.style			= CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW | CS_OWNDC ;
    		
	    wndClass.lpfnWndProc	= WndProc;
    		
	    wndClass.cbClsExtra		= 0;
	    wndClass.cbWndExtra		= 0;
	    wndClass.hInstance		= hInstance;
	    wndClass.hIcon			= hIcon;
	    wndClass.hCursor		= LoadCursor(NULL,IDC_ARROW);
	    wndClass.hbrBackground	= (HBRUSH)(iBackground+1);
    		
	    wndClass.lpszMenuName	= NULL;						
	    wndClass.lpszClassName	= szClass;
	    wndClass.hIconSm		= hIcon;	
    	
	    if (!RegisterClassEx(&wndClass)){
			WRITETOLOG("Window class register failed.");
			throw STE::STException("Window class register failed!");
	    }
		 
    }


My question is,is it ok to catch all the exceptions in the WinMain like this :

int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int iCmdShow)
{
	
	
	try
	{
		STE::Log::createFile("logGame.txt");
		WRITETOLOG("Dark Age Started!");
	
		int loopResult = 0;	
		boost::shared_ptr<DAGameApp> gameApp(new DAGameApp);
	
		loopResult = (int)gameApp->messageLoop(hInstance);
	
	}
	catch(const STE::STException& e)
	{
		MessageBox(NULL,"dd","dd",MB_OK);
	}	
	
	WRITETOLOG("Dark Age Ended!");
	_CrtDumpMemoryLeaks();

	return loopResult;
}


With this I will only have one try catch. 1)What is the disadvantage of this? 2)STException is derived from std::runtime_error.Will it catch all runtime_exceptions? or do i need another catch(std::runtime&) 3)When i throw a exception messageBox crashes.Any idea why? (Nevermind this. I figured out why it was crashing.I wasnt calling DestroyWindow in the window class destructor.Adding it to the destructor fixed it.) Sorry for the long post :P [Edited by - Black Knight on June 25, 2008 2:18:06 PM]
Advertisement
Quote:Original post by
2)STException is derived from std::runtime_error.Will it catch all runtime_exceptions?


No. Other exceptions that derive from std::runtime_error are completely unrelated to your class (i.e., they are not sub-classes of it), so this catch clause will not catch them. To catch all std::runtime_error type exceptions, the type of exception caught should be std::runtime_error.

Note also that std::runtime_error derives from std::exception, as does std::logic_error.

I recommend that you read this chapter on exception handling from Thinking in C++, Vol. 2. It is pretty in-depth.

Quote:1)What is the disadvantage of this?


Well one disadvantage is that in the catch clause, you don't know much about the error so you have no way to recover from it. If there are several errors that can happen in your application and each one requires different handling, you'll need to create several exception types and have several catch clauses. Then in each catch clause, you'll know what the error is and so you might be able to handle it better (instead of just exiting the program).

Another possible disadvantage is that exceptions can introduce overhead. See here for more info and additional advice.
So my exception class can only catch std::runtime_error exceptions and not any other derived from std::runtime_error.
Is there somewhere I can see the exceptions that derive from runtime_error?
I'm trying to figure out what errors to handle with exceptions and what with error codes.
For example I found out that iostream exceptions ios_base::failure is off by default.So If a file is not found ifstream doesnt throw any exceptions.
My main question is what should I catch?Currently I throw an exception if window class registration fails and if window creation fails I throw a STE::RunTimeException.And I added a bad_alloc exception to check if new fails.
Here is the winmain :
/* *	Dark Age Entry Point */#include "DAGameApp.h"#include "STUtil.h"#include "STException.h"int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int iCmdShow){		int loopResult = 0;		try	{		STE::Log::createFile("logGame.txt");		WRITETOLOG("Dark Age Started!");			boost::shared_ptr<DAGameApp> gameApp(new DAGameApp);			loopResult = (int)gameApp->messageLoop(hInstance);		}	catch(const STE::RunTimeException& e)	{		STE::STErrorBox(NULL,e.m_Error,e.m_File,e.m_Function,e.m_Line);	}	/*	 *	Catch exceptions from new	 */	catch(const std::bad_alloc& e)	{		MessageBox(NULL,e.what(),"std::bad_alloc exception",MB_OK);		}	catch(...)	{		STE::STMsgBox(NULL,"some unhandled exception occured!","Exception.");	}		WRITETOLOG("Dark Age Ended!");	_CrtDumpMemoryLeaks();	return loopResult;}

What should I do if a file is not found?Should i check myself for the fail bit of the stream and throw a STException?Or should i enable exception throwing for the stream?
Quote:Original post by Black Knight
What should I do if a file is not found?Should i check myself for the fail bit of the stream and throw a STException?Or should i enable exception throwing for the stream?


You need to throw an exception of a type that describes what happened. Not finding a file is not merely a runtime error, it's also a file-not-found error. This means that if the code can go on without loading the file, it can catch that particular exception and compensate.
Quote:Original post by Black Knight
So my exception class can only catch std::runtime_error exceptions and not any other derived from std::runtime_error.


Still not quite correct. Using the catch clause in your snippet:

try{	// ...}catch(const STE::STException& e){	MessageBox(NULL,"dd","dd",MB_OK);}


You will catch any STE::STException exceptions and any exceptions derived from STE::STException. std::runtime_error is not derived from STE::STException and thus won't be caught at all by your catch clause.

The "catch" chooses what to catch, NOT the exception. The exception "falls" until a "catch" is type-compatible with it.

'catch' follows the same 'if it can be implicitly typecast, I can handle it' rules as function overloading.

for instance:

void func(XYZ *a) // handles any argument that is, or derives from XYZvoid func(ABC *b) // handles any argument that is, or derives from ABCtry{  // anything you want}catch (const XYZ &a){  // catches any exception that is, or derives from XYZ}catch (const ABC &b){  // catches any exception (NOT CAUGHT BY THE FIRST CATCH) that is, or derives from ABC.}


The difference here is that if you make a class that derives from BOTH XYZ and ABC, and try to call "func" on it implicitly, the compiler will yell at you that it's ambiguous. With the catch, it's first-come-first-serve and the compiler won't yell at you.
Quote:Original post by Black Knight
Is there somewhere I can see the exceptions that derive from runtime_error?


The chapter I linked to presents the full hierarchy of standard exceptions.

BTW, recently I created an exception class that stores info about where it occurred. If you're interested, here it is:

#include <string>#include <sstream>#include <exception>class Exception : public std::exception {public:	Exception(const std::string &msg, long ln, const std::string &fl, const std::string &func)		: desc(msg), line(ln), file(fl), function(func)	{}	const char * what() const {		std::ostringstream os;		os << "Error: " << desc << "\n";		os << "Function: " << function << "\n";		os << "Line: " << line << "\n";		os << "File: " << file << "\n";		buffer = os.str();		return buffer.c_str();	}private:	std::string desc;	long line;	std::string file;	std::string function;	mutable std::string buffer;};// Some compilers don't define this#ifndef __FUNCTION__#define __FUNCTION__ "Unknown"#endif#define THROW(msg) throw Exception(msg, __LINE__, __FILE__, __FUNCTION__)


I'm not sure the whole business with mutable is actually necessary, but it solved some lifetime issues and I was too lazy to think of something better. Feel free to improve it.
Quote:Instead I created a method like bool create(parameters) to create the object.
And I destructed the object in the destructor.

That's a serious design issue, because between your variable declaration and the create call you have inconsistent state. It initializes all members and bases to an empty or invalid state before being correctly reassigned. And if create is not called, everything is a mess, the destructor can segfault etc, who knows.
How do you handle copies also? Assignment?

That's really a stupid design when C++ handles specifically all that stuff for you.
Quote:Original post by loufoque
That's really a stupid design when C++ handles specifically all that stuff for you.


Well, yes, this is why he was looking at exceptions [wink]
I have a half-written blog post on this, so I'll paraphrase it here :)

My exception design guidelines:

1. derive from the most appropriate standard library exception.

Sometimes this is simply std::exception, but it could be std::logic_error, std::bad_cast, etc. Lots of code relies on this i.e. it tries to catch(some_std_exception).

2. Don't rely on strings to convey the reason that the exception was thrown.

If one exception type is capable of representing numerous error conditions, you must parse the string to find out what went wrong at the catch site. If the content of the string changes over time, then you have to notify all clients of your code (which is often impossible) that the string's format has changed.

Furthermore, with strings you assume a particular language. This is particularly bad/useless if your software is supposed to be multilingual (or if your library may be used in arbitrary pieces of software).

Lastly, is the string for suitable for a user, or should it only be read by a programmer to aid in debugging the cause? How can the client tell?

I know the standard library uses strings, but IMHO, it's a mistake for the above reasons. By all means override what(), but provide extra information from which an appropriate user-facing message can be constructed if need be.

3. Use types rather than attributes to distinguish between kinds of error.

Let's suppose that you have an exception called file_open_error. There are various reasons that a file may fail to open e.g. it doesn't exist, another application has it open for exclusive access, hardware failure, etc. It's tempting to encode these failure reasons as enumerated constants and have a method that returns the reason for failure. This is a good idea, but you should also derive one exception from file_open_error for each kind of failure.

Why? Because the exception catching mechanism works on types. If I'm in a position only to handle file_open_errors that were caused by faulty hardware, I can simply catch(const disk_failure &ex), rather than catching a file_open_error, examining the exception for the kind of error via a method and then either re-throwing or handling the error depending on the result of that method call.

Also, by creating derived exceptions you can now pack more information in to an exception. By giving the catch-site more information, you allow it to make a better informed decision about how to handle the condition. Sometimes, however, simply creating and throwing a derived object provides that extra information.

4. Exceptions should be copyable

Define a sensible copy constructor and copy assignment operator if the compiler generated one isn't appropriate. The same goes for the destructor of course. When an exception is thrown, it may be copied.

5. virtual rethrow_copy() and to_any() (i.e. boost::any) functions are damn useful for propagating exceptions across thread boundaries. I sometimes put them in a mixin.

This topic is closed to new replies.

Advertisement