Using Exceptions or Return Values

Started by
13 comments, last by GregMichael 14 years, 1 month ago
Hello, I am rewriting some classes of my little framework and I would like to ask you which one of the following ways of managing errors and exceptions you think is more appropriate:

D3DEffect.cpp:
bool D3DEffect::Initialize(...)
{
    // Do some initializing stuff

    if(error initializing the effect)
        return false;

    return true;
}

Application.cpp:
int main()
{
    RenderSystem renderSystem;
    Effect* myEffect = renderSystem->CreateEffect();
    OtherClass otherClass;

    bool returnValue = true;

    returnValue = renderSystem.Initialize(...);
    returnValue = myEffect.Initialize(...);
    returnValue = otherClass.Initialize(...);

    if(returnValue == false)
    {
        ShowCriticalMessage("Could not initialize the engine!");
        TerminateApplication();
    }

    return 0;
}
Or...

D3DEffect.cpp:
void D3DEffect::Initialize(...)
{
    // Do some initializing stuff

    if(error initializing the effect)
        throw std::runtime_error("Could not initialize the effect!");
}

Application.cpp:
int main()
{
    RenderSystem renderSystem;
    Effect* myEffect = renderSystem->CreateEffect();
    OtherClass otherClass;

    try
    {
        renderSystem.Initialize(...);
        myEffect.Initialize(...);
        otherClass.Initialize(...);
    }
    
    catch(std::exception& e)
    {
        ShowCriticalMessage(e.what());
        TerminateApplication();
    }

    return 0;
}
Should I use return values or exceptions for errors like these? I've already made a search for an answer in other threads or websites but I couldn't understand how to manage errors in functions like the initializiation one I've presented here... I hope you'll help me :) Thank you in advance. [Edited by - jwein on March 15, 2010 11:01:03 AM]
Advertisement
In general, use both, as appropriate.

Quote:
bool D3DEffect::Initialize(...)

See, I would eschew this method entirely in favor of a constructor, and thus exceptions would be the only way to report an error. Using manual initialize/cleanup methods makes it more difficult to take advantage of RAII, one of the best features offered by C++.

Quote:
Should I use return values or exceptions for errors like these?

Only your second example, using exceptions, works reasonably well. The first example only checks the return value of the last initialize call. Both examples have some pretty glaring faults but they are not directly related to the actual checking of the error condition, aside from the aforementioned issue, so I will ignore them.

In this case I would prefer the exception method anyhow.

There are no firm rules, but there are guidelines you can follow. Generally, you should prefer exceptions for things that are quite unexpected or out of the ordinary -- things that should not happen during nominal execution. Failure to initialize a complete subsystem is rather abnormal, and thus exceptions should be used.

In contrast, some other things shouldn't use exceptions. Hit testing, for example, is expected to "fail" often during execution and so shouldn't use an exception to report that failure. Similarly, loading a texture from a delay-load cache may "fail" in a fashion that isn't critical, returning a dummy texture until the real texture gets pulled in (for example).

Another way to look at the problem is whether or not the failure would be one the caller of the function can reasonably be expected to handle. This generally involves looking at the problem from a broader perspective, of course.
It mostly depends. Either is acceptable, but with exceptions, you have much more flexibility. On the other hand, exceptions are rather slow.

As a rule of thumb, use return values for non-fatal errors and exceptions for fatal errors or those requiring a good deal of information.

That being said, exceptions are being pushed more in C++. If you want to be trendy and not be yelled at for bad style by your Computer Science teacher :P, use exceptions.

Cheers,
Patrick
Thank you for your answers. Now I understand the difference between return values and exceptions...

I would like to ask you another related question: If I have, for example, a class which represents a mathematical vector and I would like to check, in a function or in an operator, if a member or a parameter is correct (for example in a division if it's not equal to zero), should I use assert or an "if" condition to eventually call an abort function or throw an exception?

I know that asserts are not compiled in release mode, but the functions of mathematical classes are time critical and I don't know if using "if" conditions at runtime would be a good solution.
Quote:
Thank you for your answers. Now I understand the difference between return values and exceptions...

I would like to ask you another related question: If I have, for example, a class which represents a mathematical vector and I would like to check, in a function or in an operator, if a member or a parameter is correct (for example in a division if it's not equal to zero), should I use assert or an "if" condition to eventually call an abort function or throw an exception?

I know that asserts are not compiled in release mode, but the functions of mathematical classes are time critical and I don't know if using "if" conditions at runtime would be a good solution.

You can build your own asserts to function in release mode, if desired. The performance issue isn't worth worrying about until it shows up in the profiler, so don't stress over a few extra if statements. It is more important that something be correct rather than fast.

As to what you should actually do to cope with the error -- it depends. Probably an exception isn't ever going to be the most ideal solution for something like a math library. How you return a failure indication depends largely on the way the rest of the API is designed (i.e., do you use boost::optional? Do you let the operation continue and produce an undefined result?)

Do you have a concrete example?
If you are double checking for the programmer, assert is better. Anything that could fail in the wild should be checked with if(). You wouldn't assert() that a file was loaded, because the user could easily delete the file from their hard drive. Be very careful with assert, because any side effects won't be performed during release.

For division by zero, the hardware will issue a fault if this occurs, so I wouldn't bother asserting - although be aware that you or a library can mess with the floating point hardware and disable this check.
Personally speaking, I'd use an Assert() which can be compiled out for a release build and live with slower speed in the debug build. Get it working first...:)

Regarding exceptions or checking return values, again my opinion - use whatever gives the "end user" the cleanest experience...eg. report errors nicely and with as much information as necessary and do it gracefully.

Just don't crash his PC :)
IMHO assertions cover another, 3rd kind of failure besides the 2 jpetrie has mentioned in this answer above. Assertions are good for Meyer's Design by Contract, i.e. to check pre-conditions and perhaps post-conditions when a routine is called: "If the calling client guarantees the pre-conditions, then this routine will work fine and guarantees the post-conditions." But you cannot impose each and all runtime conditions to the caller, or perhaps you don't want do so (e.g. due to information hiding).
@jpetrie, an example could be the following:
In class Vector3D:Vector3D& operator/= (float scalar){	assert(scalar != 0.0f);	x /= scalar;	y /= scalar;	z /= scalar;	return *this;}

Or, in class Matrix4D:
void MakeProjectionMatrixFovLH(float fieldOfView, float aspectRatio, float nearPlane, float farPlane){	assert(fieldOfView != 0.0f && aspectRatio != 0.0f && (farPlane - nearPlane) != 0.0f);	const float h = 1.0f / tan(fieldOfView / 2.0f);	const float w = (h / aspectRatio);	m[0][0] = w;	m[0][1] = 0.0f;	m[0][2] = 0.0f;	m[0][3] = 0.0f;	m[1][0] = 0.0f;	m[1][1] = h;	m[1][2] = 0.0f;	m[1][3] = 0.0f;	m[2][0] = 0.0f;	m[2][1] = 0.0f;	m[2][2] = farPlane / (farPlane - nearPlane);	m[2][3] = 1.0f;	m[3][0] = 0.0f;	m[3][1] = 0.0f;	m[3][2] = (-nearPlane * farPlane) / (farPlane - nearPlane);	m[3][3] = 0.0f;}

Here I've used assertions, but I can't be sure that the parameters I used in debug mode will be the same in release mode because they could be loaded by file or in another ways. I don't know if I'm managing well parameter checking in these classes...
You could always gracefully handle the errors. Pick a reasonable default if the parameters are outside bounds in release, in debug simply assert().

This topic is closed to new replies.

Advertisement