Skip to main content
GameDev.net gamedev.net
🔒 Locked

Question on exception usage

Started by kirkd Mar 14, 2007 at 5:01 PM 15 replies 1.9k views
Original Post
kirkd
kirkd
I have put together an exception class of my own (code below if you're interested), that allows me to catch exceptions, feed error strings to a log file, and effectively identify where things go awry. The problem is, my code ends up either being in one huge try...catch block or numerous try...catch blocks around basically every fuction call. What am I missing? Is it fundamentally wrong to use exceptions in this way? -Kirk Please use [source lang="cpp"][/source] tags for large chunks of source code. Thanks - Emmanuel ExceptionRKD.h

/*	ExceptionRKD
	Robert Kirk DeLisle

	Purpose:	Class to be used in exception handling.

	Modification History:
*/

#if !defined(ExceptionRKD_06May2004_INCLUDED)
#define ExceptionRKD_06May2004_INCLUDED

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

#include <ostream>
#include <string>
using namespace std;

class ExceptionRKD
{
private:

	long m_lErrCode;	//contains an error code value
	const char *m_cMessage;	//an error message
	const char *m_cFunction;	//will contain the name of the function which generated the error

	ExceptionRKD();	//force a construction method

public:

	ExceptionRKD(long ErrCode, const char *FunctionName, const char *Message)
	{  m_lErrCode=ErrCode; m_cMessage=Message; m_cFunction=FunctionName; }

    long ErrorCode() const;
        //return the error code currently in the exception

    string ErrorMessage() const;
        //return the error message currently in the exception

    string ErrorFunction() const;
        //return the name of the function currently in the exception

	~ExceptionRKD() {}

friend ostream& operator<<(ostream& s, const ExceptionRKD &ex);
};

#endif



ExceptionRKD.cpp

/*	ExceptionRKD implementation
	Robert Kirk DeLisle

	Purpose:	Class to be used in exception handling.

	Modification History:
*/

#include "ExceptionRKD.h"

ostream& operator<<(ostream &s, const ExceptionRKD &ex)
{
	/*	Robert Kirk DeLisle

		Purpose:	Allow streaming output of exception contents.

		Parameters:	s	-	reference to the ostream object
					ex	-	reference to the ExceptionRKD object

		Return:
	*/

	s << ex.m_lErrCode << " : " << ex.m_cFunction << " : " << ex.m_cMessage;

	return s;
}

long ExceptionRKD::ErrorCode() const
{
    /*	Robert Kirk DeLisle

		Purpose:	Return the ErrorCode value

		Parameters:	none

		Return:     The error code value currently in the exception.
	*/

    return m_lErrCode;	//contains an error code value
}

string ExceptionRKD::ErrorMessage() const
{
    /*	Robert Kirk DeLisle

		Purpose:	return the error message currently in the exception

		Parameters:	none

		Return:     The error message currently in the exception.
	*/

    return string(m_cMessage);
}

string ExceptionRKD::ErrorFunction() const
{
    /*	Robert Kirk DeLisle

		Purpose:	return the name of the function currently in the exception

		Parameters:	none

		Return:     The name of the function currently in the exception.
	*/

    return string(m_cFunction);
}



[Edited by - Emmanuel Deloget on March 15, 2007 10:14:45 AM]
ApochPiQ
ApochPiQ
What makes you think your exception class is the cause of this?

Most certainly the problem is how you use exceptions, not the type of exception you throw. Post and/or describe your code where you're using exceptions if you want advice on your usage of exceptions [wink]
kirkd
kirkd
Your point is well taken, and I didn't explain myself very well. I suppose the development of my exception class is really superfluous information. Usage of exceptions is the real question I'm getting at.

The real heart of the question is, can I use exceptions without leading to enormous usage of try...catch?? What are the acceptable use cases for exceptions and the unacceptable use cases?

-Kirk
ToohrVyk
ToohrVyk
When to throw? First, a function, any function, should have a detailed main objective, which considers what should happen in a perfect happy world where nothing ever goes wrong about the user, the filesystem, the memory, and so on. The signature of a function, in this approach, is designed without considering the possibility of failure.

Then, it should be accepted that a function can fail during normal program use, because of some external causes: not enough memory available, network connection problems, syntax error in a configuration script, nonexistent or inaccessible file, and all the other errors which can occur exceptionally. The idea here is to move away as many such small details from the main objective (which should be simple and described in one or two sentences tops) into an exceptional failure list.

So, a function attempts to achieve its main objective, and throws an adequate exception when it can't.

When to catch? The main idea is, don't catch if you cannot handle the exception. For instance, a read next byte function can't do a thing about a network connection problem, so it doesn't attempt to catch that particular exception.

So, if a particular exception prevents a function from accomplishing its main objective, then that function shouldn't attempt to catch it (sometimes, however, it might be advantageous to translate the exception into another type). An exception should only be caught if there is a hope of still accomplishing the main objective, despite the error.
_the_phantom_
_the_phantom_
Quote:
Original post by kirkd
The real heart of the question is, can I use exceptions without leading to enormous usage of try...catch?? What are the acceptable use cases for exceptions and the unacceptable use cases?


Well, exceptions are for exceptional situations; things which shouldn't happen often.
If you are try-catching all over the place then you are probably handling the exception at the wrong level; the idea with exceptions is to let them propigate back up the call stack until you get to a point where is there is enuff information and knowledge to deal sanely with the error.
kirkd
kirkd
Emmanuel - yes, I tried using [source lang="cpp"] but notice what happens to my code. You can't actually see all of it. So, I resorted to [].

dalleboy - Right. I'm not confused about that simple level.

ToohrVyk, phantom, and ApochPiQ - Exactly what I was looking for!! Thanks!! From ApochPiQ's link, I appear to be suffering from return code mindset. I have been treating exceptions as glorified return codes and my Exception class becomes a return code logger. That particular link and specifically question #13 nails it perfectly.

-Kirk
TheUnbeliever
TheUnbeliever
Quote:
Original post by kirkd
Emmanuel - yes, I tried using [source lang="cpp"] but notice what happens to my code. You can't actually see all of it. So, I resorted to [].<!--QUOTE-->


[source] places them in a scrollable box as opposed to [code] which seems to just enclose them in teletype tags and convert soft whitespace to non-breaking spaces.
[TheUnbeliever]
_Sigma
_Sigma
So building on this, how general should your exception class be?

Mine is currently very general. When you throw it, you just tell the exception what happened, so you can write it out to a log file/message box later.
throw TException("Window failed to create. Terminating");...catch(Texception& e){ //message box or log}

I don't have a TWindow exception, a TRenderer Exception. I only have the TException exception.

I assume too broad?

Would it be wise to have a TException base class with all virtual methods, and then create situation specific exceptions inheriting from this other class? I assume there is a limit to this as well though (ie too many).

ApochPiQ
ApochPiQ
Don't choose what type of exceptions to throw based on the error - choose what type of exception to throw based on how you plan to respond to the error.

There are, in my mindset, two basic types of exceptions: ones I want to try to recover from, and ones I want to let terminate the program. In the second case, the type doesn't matter; I personally typically use a std::exception and catch it in main() or whatever and show some kind of error dialog.

The first case is where the type of exception becomes important, and it's something you will have to handle on a case-by-case basis. If your catch() code needs some particular information to know how to handle the exception, encode it in the exception itself - encode general information (the "sort" of error) by selecting the exception's class, and encode specifics (details about the error) in the exception object's member data.

General rule of thumb: if you can handle and properly react to/recover from an exception without some particular piece of information, do it. Only introduce information into your exceptions (either via exception classes or member data) when you absolutely need that information in order to avoid making assumptions about why the exception was thrown.


Hope that sheds a little light on things.
Antheus
Antheus
Are you by any chance doing this? It may sound a stupid question, but seems the only way to explain "around every function call".

[source lang=cpp]void createWindow(){...throw ...}void initWindow(){...throw ...}void resizeWindow(){...throw ...}void closeWindow(){...throw ...}.... user codevoid createApplication(){try{ createWindow() } catch (...) {}try( initWindow() } catch (...) {}try{ resizeWindow() } catch (...) {}try{ closeWindow() } catch (...) {}}
kirkd
kirkd
_Sigma,

Yes, I understand how to scroll a window, but look at my original post. I can't actually scroll all of the code. Just some of it.

Antheus,

I had a tendency to do this:

try{   object.Initiazlize();}catch(ExceptionRKD &ex){   logfile << ex;}try{   object.DoSomething();}catch(ExceptionRKD &ex){   logfile << ex;}try{   object.DoSoemthingElse();}catch(ExceptionRKD &ex){   logfile << ex;}


This is a bit of an exaggeration, but not much.

-Kirk

Once again, the [source lang="cpp"] tags aren't working for me. The code block above should have three try...catch blocks for three functions: Initialize(), DoSomething(), and DoSomethingElse(). Can anyone see all three??
[/source]
MaulingMonkey
MaulingMonkey
Key to exceptions is that they interrupt the current flow of the program, and discard it.

At least, up to the exception handler.

This would be the kind of source I'd expect from proper use of exceptions:

try{   object.Initiazlize();   object.DoSomething();   object.DoSomethingElse();}catch(ExceptionRKD &ex){   logfile << ex;}


DoSomethingElse() never happens if an exception occurs in DoSomething(), because exceptional circumstances have made that entire chain of execution irrelevant somehow. Examples would be a socket closing in the middle of a communication negotiation:

try{    socket.connect( ... );    socket.send_header( ... );    socket.recv_server_header( ... );    socket.send_credentials( ... );    socket.recv_authorization( ... );}catch( connection_lost ) {    ...report the error to the user...}


C++ Exceptions are "fatal", up to the point where they are caught and dealt with, and to which no useful concept of "continuation" exists. Think "bail out".

This should not be confused with an exceptional circumstance which should be made note of, after which execution should continue as normal, which seems to be the situation you're in if you're adding a new catch block for every function called. The C++ exception mechanism is a very poor method to choose for logging.

Ruby's equivilant to the C++ "catch" is "rescue". You shouldn't have to rescue yourself from log messages.
_Sigma
_Sigma
Quote:
Original post by kirkd
_Sigma,

Yes, I understand how to scroll a window, but look at my original post. I can't actually scroll all of the code. Just some of it.

WTF are you talking about?

Quote:

This would be the kind of source I'd expect from proper use of exceptions:
try{   object.Initiazlize();   object.DoSomething();   object.DoSomethingElse();}catch(ExceptionRKD &ex){   logfile << ex;}


Thats exactly how mine looks. That makes me feel better =)
kirkd
kirkd
Internet Explorer, latest version, updates, patches, etc. I only get the first third of the source displayed when I scroll. I cannot scroll through the complete content of the source box.

Thanks for the explanation Monkey.

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.