Click here to Skip to main content
Click here to Skip to main content
Go to top

A point on exceptions

, 20 Oct 2011
Rate this:
Please Sign up or sign in to vote.
Exceptions from yet another angle.

Introduction

Exceptions in C++ are very frequently explained through the syntax elements which in essence do not explain thoroughly how it all works. Here, we will create some examples to explain how it really works and what options a developer has.

Background

If you take another syntax element, like the for or while loop, it is almost always very clearly explained what is going on and how a developer should use it, what the possible pitfalls are, etc. However, for some strange reason, I could not find a clear and concise explanation on exceptions that goes down to the same depth. There are several hidden penalties, there are even more possibilities within exceptions which you should be aware of.

This article does not teach you what the best way or best practice is on its own. This is better a collection of examples explained through general efficiency that will help you decide what works best for you in shaping a stable system of exceptions in your code.

The main purpose of the exception mechanism is to:

  • provide a safe exit from an unusual but planned situation
  • give information about the type of the unusual event
  • offer more detailed information about each event
  • set back the program execution point to a safer location which is less affected by the event

The code samples

We will use a step by step approach. First, let us see a general exception class or structure details.

// a class used as an exception, we've pointed
// constructors, copy constructor and destructor
class ExcTest
{
    private:
    int info;

    public:
    ExcTest()
    {
    }
    ExcTest(int nInfo)   // a preferable way of instantiating an exception
    {
        info = nInfo;
    }
    ExcTest(ExcTest& exc)
    {
    }
    void ChangeInfo(int nInfo)   // used to change info for an existing exception
    {
        info = nInfo;
    }
    ~ExcTest()   // expose the destructor to track the exception life span
    {
    }
};

In normal circumstances, any information field should be hidden as private or protected. Use the empty constructor like ExcTest() above if you want to throw an empty exception which would hold only the information about the exception type (read below about other ways of doing the same). Use the constructor to create an informative exception. In normal circumstances, the access members to the information fields, like ChangeInfo above, should not be the principal way of setting the information about an exception.

Basic type example

try
{
    throw 5;
}
catch (int i)
{ 
}

We are catching the basic type, a copy of a variable will be used as it would with any other function call that has (int i).

// somewhat more efficient basic type throw-catch
try
{
    throw 5;
}
catch (const int& i)
{
}

This is more efficient since we are using a reference and claim it as constant (if i is changed, we can remove const and still get the benefit of a reference usage at least).

Throwing by value

try
{
    ExcTest exc;    // a local exception variable created on the stack
    throw exc;      // exc is first copied once before its copy is thrown,
                    // original exc is destroyed
                    // throw should be understood the same way as } or return
                    // because they all exit the current scope
}
catch (ExcTest epass) // now, here, the exception thrown is copied again
                      // before it is passed into the catch 
{
}

With this (not that rare) usage, the same exception is copied twice, created three times, before it is caught. Some information about the exception could be lost depending on the process of copying. Observe that each time we mention copying, a copy constructor of the exception type, class, or structure must be accessible.

// more precise catch, by const reference    
try
{
    throw ExcTest(123);    // local variable of required type 
                           // created on the stack
                           // and, as the syntax suggests,
                           // it is not copied just thrown
}
catch (const ExcTest& epass)   // the exception thrown is not copied
{
}

With this usage, the same exception is not copied at all before it is caught. No information about the exception is lost. The requirement is that we need a constructor that will be able to record the information about the exception. (You might find yourself in a strange situation that you have to expose the copy constructor in the class of an exception, although you have no intention of using it. This proves that the compiler is scratching the head on exceptions.)

Double catch

try
{
    try
    {
        throw ExcTest(123); // local variable of required type is
                            // created on the stack
                            // and then thrown as it is, without copying
    }
    catch (ExcTest& epass)  // epass is not copied just passed,
                                // no const used since we need copying later 
    {
        throw epass;  // this will unfortunately make a copy of epass,
                      // destroying the original and throwing the copy
    }
}
catch (const ExcTest& epass)  // epass is not copied just passed 
{ 
}

With this usage, the same exception is still copied once before it is caught. Some information about the exception could be lost.

// precise double catch
try
{
    try
    {
        throw ExcTest(123); // local variable of required type
                            // created on the stack
                            // and then thrown as it is without copying
    }
    catch (const ExcTest& epass)   // epass is not copied just passed, 
                                       // const helps to speed up the catch call 
    {
        throw;   // throw the same exception again
    }
}
catch (const ExcTest& epass) 
{ 
};

Now, we are dealing with the same exception as the original one all the time.

Throwing by pointer

try
{
    ExcTest* exc = new ExcTest(123);
    throw exc;    // exception object is now created on the heap        
                  // there is no copying of the object    
}
catch (ExcTest* epass) // cannot use const because we have to delete it
{
    delete epass;    // unfortunately we have to delete the exception object
                     // unless we are throwing it again
}

This is a correct usage, but we have to deal with new and delete, which can throw an exception on their own. (Imagine we are catching an out of memory exception.)

// more precise throw-catch by pointer
try
{
    throw new ExcTest(123);   // exception object created on the heap
                              // no copying of the object
}
catch (const ExcTest* const epass)  // speeding up the catch call
                                    // each const could be removed, of course
{
    delete const_cast<ExcTest*>(epass);  // if we use constant
                                               // we have to now convert
                                               // the pointer 
                                               // from constant so we can 
                                               // delete it
}

This is a complete, although excessive, picture of how we can create a precise catch using pointer.

Exotic usages that shed more light on the subject

// throwing exception type without copying
try
{
    ExcTest exc;        // local exception
    throw (ExcTest*)&exc;    // exc is deleted, destroyed, 
                                 // but a new object is not created
                                 // we've added (ExcTest*) for clarity
}
catch (ExcTest*) 
{
}

As you can notice, we did not pass a variable name, it is only catch(ExcTest*). This is because a variable passed would be a zombie, previously destroyed one, however we still have the information about the exception type (pay attention that some compiler exception mechanisms would make the previous exception, although destroyed, still available and even readable, but using it would be equal to playing with fire).

// throwing exception type only
try
{
    throw (ExcTest*)NULL;    // no object just exception type
}
catch (ExcTest*)
{
}

We did not pass a variable name since it would be NULL, however we have information about the exception type. Knowing that this is possible, it is not bad to always check an exception pointer for NULL.

Global static usage

// throwing by value without copying
try
{
    static ExcTest exc;     // global stack is used, and
                            // the object created only once
    exc.ChangeInfo(132);    // set local information
    throw (ExcTest*)&exc;    // exc is not copied before it is thrown,
                                 // neither destroyed
} 
catch (const ExcTest* const epass) // the thrown exception is not copied
                                   // again before it is passed into catch 
{
}

If we use a static exception, the same exception is always used in this scope, and although we are dealing with pointers, there is nothing to delete. Of course, multithreading can create some problems, but not always critical ones, since most of the time we are interested in the exception type.

Usage within function

void rFunct()
{
    throw ExcTest(123); // exception is not copied before it is thrown
}
void lFunct()
{
    try
    {
        rFunct();
    }
    catch (const ExcTest& epass)
    {
        throw; // throw again the same exception
    } 
} 
void tFunct()
{
    try
    {
        lFunct();
    }
    catch (const ExcTest& epass)    // a previously locally thrown variable has 
                                        // a temporary global catch scope
    {
    } 
}

If we are always as precise as given in this example, the throw-catch can be as efficient as having a global static exception object.

Non-basic type exceptions

To complete the story, we will add that we can use any data type for an exception. Here is an example of using a pointer to a function as the exception type.

int sampleFunc(int i)
{
    i+=2;
    return i; 
} 
void tryFunct() 
{ 
    try 
    { 
        throw &sampleFunc;
    }
    catch(int (*calc)(int))
    {
        int t = calc(5);
    }
}

It is always up to you to decide which benefits of a type you want. Pointer to a function gives: static memory access, type information, no additional information about the exception, and some additional functionality since we are passing a function. This and similar ideas can produce some innovative exception designs in your program.

Conclusion

  1. Every throw by value creates a copy of the exception object unless we follow a strict syntax of throw [anonymous instance]; and later just throw;. If we miss that, deeply nested catches can quite lose their performance on the way out and we can lose information about the exception. Of course, additionally, we should always catch a value by constant reference (unless we need to change the exception object when we can use only a reference).
  2. Throwing by pointer is possible and has a more clear syntax, you can create a variable anywhere; however, the problem is that we are dealing with an exception while we are asking for memory juggle on the heap, which can throw an exception of its own, and the second problem is that we have to delete the exception object somewhere later. A smart pointer could simplify that, but deleting is still there just hidden. Unless planned in advance, the code within a catch block should not be able to throw an exception.
  3. Throwing by a reference converted into the pointer is an efficient way, but we would safely know only the exception type. For that purpose we can use the cast of NULL equally well. If we throw or catch just an exception type, the exception variable passed within a catch block remains anonymous.
  4. Throwing by a static reference with the cast into a pointer is very efficient, providing that we calculate in advance the size of all exception objects used that way, since they are created on the global stack. However, if we compare this last with the properly treated case 1., they are almost equally efficient. Still, if a module has a set of typical and small number of exceptions thrown frequently, creating them as static might not be a bad idea. You can create a global set of exceptions on the heap as well.

Any choice made should fit well with those exceptions that are thrown internally by library functions.

Points of interest

It looks like the exception system is somewhat clumsily designed regarding the required syntax. If you use constant reference for an exception within catch, throw an anonymous instance and later throw again the same exception using only throw; the entire scope of chained catch blocks appears shared, regardless of the local function's, module's, or other scopes. If you miss any of these, the shared scope is suddenly cut into individual areas where an exception is copied and recreated again and again. On the other hand, knowing that catch areas can be easily shared among totally unrelated functions or modules (in essence, we can create a local variable that has a temporary global scope) offers some new insights into the possible architectural solutions.

Now that you have, we hope, a clearer picture of how it really works, you can use the method that suits your needs best. However, if you follow the set of rules:

  • create an informative exception only through the constructor
  • throw [anonymous instance];
  • catch by constant reference
  • throw again the exception using just throw;

most of the problems about exception usage should be resolved. Using pointers, however, does put some new design moments into perspective.

Corresponding design pattern

Additionally, you can try to protect the code against undesirable effects of the syntax peculiarities through the design of the class for exceptions. For example, you can remove or make a default constructor private or protected, or make the copy constructor private or protected. That way a compiler will complain about less efficient syntax immediately. (Some older compilers will object to hiding the copy constructor in an exception type, even though it is not used. Do not be surprised if throw can access your copy constructor even when it is set private. Exceptions are, as we explained, not completely charted read: compiler tested territory). If you want to use pointers, make adequate adjustments.

// a typical design of an exception class that follows the guidelines,
// you can further derive your exception classes

template<typename e_Info>
class BaseException 
{
    protected:   // replace with private: in case the class
                 // will not be a base class
        e_Info info;
        BaseException()
        // explicitly set as protected (private),
        // as within a design decision
        {
        }
        BaseException(BaseException& exc)
        // hidden, so compiler would complain
        {
        }  
    public:
        BaseException(e_Info qInfo)
        {
            info = qInfo;
        }
        ~BaseException()
        {
        }
};

History

  • Sunday, October 16 2011, 11:15 AM, first version.
  • Tuesday, October 21 2011, 12:45 AM, general template design added.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

alexpeter

Norway Norway
No Biography provided

Comments and Discussions

 
GeneralMy Vote of 5 PinmemberRaviRanjankr20-Nov-11 4:21 
GeneralMy vote of 4 PinmemberRakesh Meel18-Oct-11 23:14 
Nice n helpful example
QuestionMy vote of 4 PinmemberNiklas Lindquist16-Oct-11 9:53 
AnswerSure, but... Pinmemberalexpeter16-Oct-11 10:25 
QuestionGood article PinmvpRichard MacCutchan16-Oct-11 7:08 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.140916.1 | Last Updated 20 Oct 2011
Article Copyright 2011 by alexpeter
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid