Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C++/CLI

C++ Exceptions: Pros and Cons

4.91/5 (105 votes)
28 Dec 2009CPOL25 min read 276K  
An unbiased analysis of good and bad cases for using exceptions vs. error codes.
  1. Introduction
  2. Arguments for using Exceptions
    1. Exceptions separate error-handling code from the normal program flow and thus make the code more readable, robust, and extensible.
    2. Throwing an exception is the only clean way to report an error from a constructor.
    3. Exceptions are hard to ignore, unlike error codes.
    4. Exceptions are easily propagated from deeply nested functions.
    5. Exceptions can be, and often are, user defined types that carry much more information than an error code.
    6. Exception objects are matched to the handlers by using the type system.
  3. Arguments against using Exceptions
    1. Exceptions break code structure by creating multiple invisible exit points that make code hard to read and inspect.
    2. Exceptions easily lead to resource leaks, especially in a language that has no built-in garbage collector and finally blocks.
    3. Learning to write Exception safe code is hard.
    4. Exceptions are expensive and break the promise to pay only for what we use.
    5. Exceptions are hard to introduce to legacy code.
    6. Exceptions are easily abused for performing tasks that belong to normal program flow.
  4. Conclusion

1. Introduction

Exceptions have been a part of C++ since early 1990s and are sanctioned by the standard to be the mechanism for writing fault-tolerant code in this language. However, many developers for various reasons choose not to use exceptions, and voices that are skeptical of this language feature are still numerous and loud: Raymond Chen's article Cleaner, more elegant, and wrong, Joel Spolsky's blog Exceptions, and Google C++ Style Guide are some of the frequently quoted texts that advise against the use of exceptions.

Rather than taking a side in this debate, I am trying to present a balanced view of pros and cons of using exceptions. The purpose of this article is not to convince readers to use exceptions or error codes, but help them make an informed decision that is best for their particular project. I have structured the article as a list of six arguments for using exceptions and six arguments against it that can often be heard in the C++ community.

2. Arguments for using Exceptions

2.1 Exceptions separate error-handling code from the normal program flow and thus make the code more readable, robust, and extensible.

To illustrate the point, we'll compare the usage of two simple socket libraries that differ only in the error handling mechanism. Here is how we could use them to fetch HTML from a web site:

C++
// sample 1: A function that uses exceptions

string get_html(const char* url, int port)
{
    Socket client(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    client.connect(url, port);
    
    stringstream request_stream;
    request_stream << "GET / HTTP/1.1\r\nHost: " 
       << url << "\r\nConnection: Close\r\n\r\n";

    client.send(request_stream.str());

    return client.receive();
}

Now, consider a version that uses error codes:

C++
// sample 2: A function that uses error codes

Socket::Err_code get_html(const char* url, int port, string* result)
{
    Socket client;
    Socket::Err_code err = client.init(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (err) return err;
        
    err = client.connect(url, port);
    if (err) return err;
    
    stringstream request_stream;
    request_stream << "GET / HTTP/1.1\r\nHost: " << url 
       << "\r\nConnection: Close\r\n\r\n";

    err = client.send(request_stream.str());
    if (err) return err;

    return client.receive(result);
}

In both cases, the code does the same thing, error handling is delegated to the caller, resource cleanup is performed by destructors. The only difference is that in the former case, the socket library throws exceptions in case of failure, and in the latter case, error codes are used.

It is easy to notice that the sample with exceptions has cleaner and simpler flow, without a single if branch to disrupt it. Error handling is completely hidden and only the "normal" code flow is visible. The infrastructure for propagating exceptions is generated by the compiler: in case of an exception, the stack will "unwind" correctly, meaning that local variables in all stack frames will be destroyed properly - including running the destructors.

In fact, sample 2 as shown here is probably even simpler and cleaner than it would be in most cases: we are using only one library within the function, and return that library's error code. In practice, we would probably have to take into account different error code types that we would hit within the function, and then we would need to map all these error codes to the type we are returning from the function.

What about robustness? When exceptions are used, it is the compiler that produces the code for the "error path" and the programmer does not do it manually, meaning there are less opportunities for introducing bugs. That is especially important when the code is changed - it is very easy to forget updating the error-handling part when a change is introduced to the normal code path, or introduce a bug when doing so.

2.2 Throwing an exception is the only clean way to report an error from a constructor.

The purpose of a constructor is to establish the class invariant. To do that, it often needs to acquire system resources or in general perform an operation that may fail. If a constructor fails to establish the invariant, the object is in invalid state and the caller must be notified. Clearly, error codes cannot be used for that purpose because a constructor is not allowed to return a value. However, an exception can be thrown from a constructor in case of failure, and by doing this, we prevent creation of an invalid object.

What are some alternatives to throwing an exception from a failing constructor? One of the most popular is the "two-stage construction" idiom that is used in our sample 2. The process of constructing an object is split into two steps: the constructor performs only the part of initialization that cannot fail (i.e., setting primitive type members), and the part that can fail goes into a separate function that usually has a name such as init() or create() and returns an error code. Establishing the class invariant in this case involves not only constructing the object, but also calling this other function and checking the returned error code. The downsides of this approach are pretty obvious: it takes more work to correctly initialize an object and it is easy to end up with an object in invalid state without knowing it. Furthermore, copy construction cannot be implemented this way - there is no way to tell the compiler to insert the second step and check for the error code. Having said that, this idiom is pretty effectively used in many libraries, and with some discipline, it may be successful.

Another alternative to throwing an exception from a constructor is maintaining a "bad state flag" as a member variable, setting that flag in the constructor, and exposing a function to check the flag. The standard IO streams use this approach:

C++
// sample 3: Use of the bad state flag

ifstream fs("somefile.txt");
if (fs.bad())
    return err_could_not_open;

This technique is similar to the two-stage construction idiom. It requires an additional data member - the state flag which can be a prohibitive expense in some scenarios. On the other hand, copy construction can be implemented this way, although it is far from being safe - for instance, standard containers do a lot of copying internally and there is no way of making them check for the state flag. However, just as with the two-stage construction, this approach can work as long as we know what we are doing.

Other alternatives include setting some global value such as errno in the constructor and hoping that the caller would remember to check it. This approach is clearly inferior to the previous ones and we won't discuss it here further.

2.3 Exceptions are hard to ignore, unlike error codes.

To illustrate this argument, all we have to do is remove error checking from sample 2 - it will compile just fine and as long as there are no run time errors, it will work fine as well. However, imagine there was an error in the call to init(): the object client will be in invalid state and when its member functions are invoked, pretty much anything can happen depending on the internal implementation of the Socket class, Operating System, etc.; the program may crash immediately or it can even execute all the functions but do nothing and return without any sign that something went wrong - except for the result. On the other hand, if an exception was thrown from the constructor, the invalid object would have never even been constructed and the execution would continue at the exception handler. The usual phrase is: "we would get an exception right in our face".

But is it really that hard to ignore an exception? Let's go up the stack and see the caller of get_html():

C++
// sample 4: "Swallowing exceptions"

try {
    string html = get_html(url);
}
catch (...)
{}

This horrible piece of code is known as "swallowing exceptions" and the effect is not much different than ignoring error codes. It does take more work to swallow exceptions than to ignore error codes and these constructs are easier to detect during code reviews, but the fact is that exceptions are still pretty easy to ignore, and people do it.

However, even if they are easy to ignore, exceptions are easier to detect than error codes. On many platforms, it is possible to break when an exception is thrown if the process is run from a debugger. For instance, GNU gdb supports "catchpoints" for that purpose, and Windows debuggers support the option "break if an exception is thrown". It is much harder, if not impossible, to get a similar functionality with error codes.

2.4 Exceptions are easily propagated from deeply nested functions.

We are often not able to deal with an error at the point it is originally detected. We need to propagate the information about the error to the level where we can handle it, and exceptions enable jumping directly to the handler without writing all the plumbing manually.

Back at our sample 1 with the Socket class. Assume get_html() is invoked by a function get_title() that gets called by a UI event handler. Something like:

C++
// sample 5: A function that calls sample 1 get_html()

string get_title(const string& url)
{
    string markup = get_html(url);

    HtmlParser parser(markup);
    
    return parser.get_title();
}

Exceptions would probably be handled at the UI event handler level:

C++
// sample 6: Exception  handler

void AppDialog::on_button()
{
    try {
        string url = url_text_control.get_text();
        result_pane.set_text(
            get_title(url);
    }
    catch(Socket::Exception& sock_exc) {
        display_network_error_message();
    }
    catch(Parser::Exception& pars_exc) {
        display_parser_errorMessage();
    }
    catch(...) {
        display_unknown_error_message();
    }
}

In the sample above, get_title() does not contain any code for propagating the error information from get_html() to on_button(). If we used error codes instead of exceptions, get_title() would need to check for the return value after calling get_html(), map that value to its own error code, and pass the new error code back to on_button(). In other words, using exceptions would make get_title() look something like our sample 1, and error codes would turn it into something similar to sample 2. Something like:

C++
// sample 7: get_title with error codes

enum Get_Title_Err {Get_Title_Err_OK, 
                    Get_Title_Err_NetworkError, Get_Title_Err_ParserError};
Get_Title_Err get_title(const string& url, string* title)
{
    string markup;
    Socket::Err_code sock_err = get_html(url.c_str(), &markup);
    if (sock_err) return Get_Title_Err_NetworkError;

    HtmlParser parser;
    HtmlParser::Err_code parser_err = parser.init(markup);
    if (parser_err) return Get_Title_Err_ParserError;
    
    return parser.get_title();
}

Just as with sample 2, sample 7 can easily get more complicated if we try propagating more specific error codes. In that case, our if branches would need to map error codes from the libraries to appropriate Get_Title_Err values. Also, note that we show only a part of the function nesting in this sample - it is not hard to imagine the work needed to propagate an error code from deep within the parser code to our get_title function.

2.5 Exceptions can be, and often are, user defined types that carry much more information than an error code.

An error code is usually an integer type and can carry only so much information about the error. Microsoft's HRESULT type is actually a pretty impressive attempt to pack as much information in a 32-bit integer as possible, but it clearly shows the limits of that approach. Of course, it is possible to have an error code that is a full-blown object, but the cost of copying such an object multiple times until it reaches the error handler makes this technique not desirable.

On the other hand, exceptions usually are objects and can carry a lot of useful information about the error. It is a pretty common practice for an exception to carry information about the source file and line that it was thrown at (using macros such as _FILE_ and _LINE_) and they can even automatically send messages to a logging system.

Why is it expensive to use objects for return error codes and not for exceptions? There are two reasons: first, an exception object is created only if an error actually happens, and that should be an exceptional event - pun intended. The error code needs to be created even if the operation succeeds. Another reason is that an exception is usually propagated by reference to the handler and there is no need to copy the exception object.

2.6 Exception objects are matched to the handlers by using the type system.

Let's expand a little on our sample 6 and display a specific error message in case the error happened when establishing the socket connection:

C++
// sample 8: Exception  handler

void AppDialog::on_button()
{
    try {
        string url = url_text_control.get_text();
        result_pane.set_text(
            get_title(url));
    }
    catch(Socket::SocketConnectionException& sock_conn_exc) {
        display_network_connection_error_message();
    }
    catch(Socket::Exception& sock_exc) {
        display_general_network_error_message();
    }
    catch(Parser::Exception& pars_exc) {
        display_parser_errorMessage();
    }
    catch(...) {
        display_unknown_error_message();
    }
}

Sample 8 demonstrates the use of the type system to classify exceptions. The handlers go from most specific ones to the most general one, and this is expressed with the language mechanism that serves the purpose: inheritance. In our sample, Socket::SocketConnectionException is derived from Socket::Exception.

If we were using error codes instead of exceptions, the error handler would probably be a switch statement and each individual case would handle a value for the error code. default would most probably correspond to catch(...). It is doable, but not quite the same. In sample 8, we use a handler for Socket::Exception to handle all exceptions from the socket library except SocketConnectionException that we handle just above it; with error codes, we would need to list all these other error codes explicitly. If we forget one, or maybe add a new error code later but forget to update it... you get the picture.

3. Arguments against using Exceptions

3.1 Exceptions break code structure by creating multiple invisible exit points that make code hard to read and inspect.

That sounds exactly the opposite of what we talked about in 2.1. How is it possible that exceptions simultaneously make the code hard to read and easy to read? To understand this point of view, remember that by using exceptions we managed to display only the "normal flow". The error-handling code still gets generated by the compiler for us and it has its own flow which is orthogonal to the flow of the code we wrote. In effect, the normal code flow is cleaner and easier to read and understand with exceptions, but that is only a part of the story. Now we need to be aware that at any function call, we introduce an invisible branch that, in case of an exception, immediately exists (or jumps to the handler if it is in the same function). That sounds worse than goto and comparable to longjmp, but in fairness, C++ exceptions are much safer to use than longjmp: as we saw in section 2.1, the compiler generates the code that unwinds the stack.

Still, the problem with readability is not necessarily bogus: if we are taking a quick look at a piece of code to learn what it does, the normal flow is really all we need, but what if we need to get really knowledgeable about it? For instance, imagine you need to do a code review or extend a function - would you rather have all possible code paths right in front of you, or having to guess which function can throw an exception and under what circumstances? Granted, it is much less of a concern if you make sure that exceptions are used only for error handling, and that the code was originally written with exception-safety in mind.

3.2 Exceptions easily lead to resource leaks, especially in a language that has no built-in garbage collector and finally blocks.

To understand what we are talking about here, let's for a moment forget object oriented programming with its classes, destructors, and exceptions, and fall back to good old C. Many functions take the following form:

C++
// sample 9: acquiring and releasing resources

void func()
{
    acquire_resource_1();
    acquire_resource_2();

    use_resources_1();
    use_resources_2();
    use_resources_3();

    release_resource_2();
    release_resource_1();
}

Basically, we acquire some resources at the beginning of the function, use them to do some processing, and then release them. The resources in question can come from the system (memory, file handles, sockets...), or external libraries. In any case, they are limited and need to be released, or may be exhausted at some point and that's what we call a "leak".

So where is the problem? Our function from sample 9 is an example of well-structured code with a single entry and a single exit, and we will always clean up the resources at the end of the function. Imagine now that something can go wrong at use_resources_1(). In that case, we don't want to execute the rest of the use_resources_... functions, but report an error and exit immediately. Well, not immediately - we need to clean up the resources first. How do we do it best with a language like C? Let's just say the discussions on that topic involve even more passion than our little "exceptions vs. error codes" dilemma: some people copy and paste the clean-up code and call it whenever they exit; other ones make macros for that purpose when possible; there are developers who preserve the "SESE" structure of the function and introduce if-else branches for each function that can fail. Such functions look like a closing angle bracket ">" and some call it "arrow anti-pattern". Many C developers use goto to jump to the cleanup part of the function.

One way or another, it is a mess even without exceptions. What if we switch to C++ and use_resources_1() to throw an exception in case of error? The way the function is written, the cleanup part will not be executed and we will have a leak. How would a garbage collector and finally blocks help here? Pretend for a moment that Java does not have networking support in the standard library and that we use something similar to our sample 1 but from Java:

Java
// sample 10: A Java version of sample 1

public String getHtml(String url, int port) throws (Socket.Exception)
{
    Socket client = null;
    try {
        client = new Socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
        client.connect(url, port);

        StringBuffer requestStream = new StringBuffer("GET / HTTP/1.1\r\nHost: ");
        requestStream.append(url).append("\r\nConnection: Close\r\n\r\n");

        client.send(requestStream.toString());

        return client.receive();
    }
    finally {
        if (client != null)
            client.close();
    }
}

In the getHtml method above, we have acquired some resources: a socket, a string buffer, and a couple of temporary string objects. The garbage collector takes care of the objects that ultimately need to release only memory to the system, and the socket is closed in the finally block which is executed when we leave the try block - whether we do it "normally" or via an exception.

C++ does not have a built-in garbage collector, nor does it support finally blocks; how come our function from sample 1 (or sample 2 for that matter) does not leak? The answer is usually called RAII (resource acquisition is initialization), and it really means that objects declared as local variables get destroyed after they go out of scope, regardless of the way they exit that scope. When they get destroyed, the destructors are executed first, and then the memory is returned to the system. It is the burden of the destructors to release all resources acquired by the objects - in fact the destructor of the Socket class used in sample 1 probably looks very much like the body of the finally block in sample 10. The best part is that in C++, we need to write this clean-up code only once and it is executed automatically each time an object goes out of scope. Another nice aspect of RAII is that it treats all resources in a uniform way - there is no need to distinguish between "GC-collectible" resources and "unmanaged" ones and leave the former to GC and clean up the latter in finally blocks: the cleanup is invisible, yet it reliably happens, much like error reporting with exceptions.

Am I saying here that the lack of a garbage collector and finally blocks is not really a reason not to use exceptions in C++ because RAII is a superior method of managing resources? Yes and no. RAII is indeed superior to the combination of a garbage collector and finally blocks, and it is a very straightforward idiom and easy to use. Yet, in order to get the benefits of RAII, it needs to be used consistently. There is a surprising amount of C++ code out there that looks like:

C++
// sample 11: An example of non exception safe code

string get_html(const char* url, int port)
{
    Socket client = new Socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    client->connect(url, port);
    
    stringstream request_stream;
    request_stream << "GET / HTTP/1.1\r\nHost: " << url 
      << "\r\nConnection: Close\r\n\r\n";

    client->send(request_stream.str());

    string html = client->receive();

    delete client;
    return html;
}

In sample 11, RAII is used for all resources except the socket - yet it is a disaster waiting to happen: if an exception is thrown anywhere in the function, we leak a socket. If someone unplugs a network cable, our program may crash very quickly. Sure, it is possible to wrap the function in a try-catch block and then close the socket both in the catch block and after it - thus simulating the non-existing finally construct, but it is exactly the kind of repetitive and tedious task that sooner or later is forgotten and we have a serious bug.

But how common this kind of code is anyway? Creating objects on stack, when possible, is not only safer but also easier than pairing new and delete - one would expect that most programmers use RAII all the time. Unfortunately, this is not really the case. There is a lot of code out there that looks like sample 11. For instance, find the official sample code for the SAX2 parser of the popular Xerces-C library - it creates objects on the heap and deletes them after they are used - in case of an exception, there is a leak. In the early 1990s, it was considered "more object oriented" to create an object on the heap even if there were no reasons to do so. Now, many young C++ programmers learn Java first at universities and write C++ code in a Java-like manner rather than idiomatic C++. One way or another, there is a lot of existing code that does not rely on deterministic destructors to automatically release resources. If you are dealing with a code base like that, the only sane thing to do is use error codes and turn the exceptions off.

Note that preventing resource leaks is only a part of the exception safety puzzle. Sometimes a function needs to be transactional: it needs either to succeed or leave the state unchanged. In this scenario, we need to roll back the operation in case of failure before leaving the function. Unsurprisingly, we again turn to destructors to trigger such an operation if an exception is thrown. This idiom is called "Scope Guard" and rather than discussing it here, I suggest reading the original article by Andrei Alexandrescu and Petru Marginean.

3.3 Learning to write Exception safe code is hard.

The usual counter argument to this statement is that error handling in general is hard, and exceptions make it easier, not harder. While there is a lot of truth in that, the issue of the learning curve remains: complex language features often do help experienced programmers simplify their code, but the beginners mostly see the added pain of learning. To use exceptions in production code, a C++ programmer not only has to learn the language mechanism, but also the best practices of programming with exceptions. How soon a beginner learns to never throw from a destructor? Or use exceptions for exceptional conditions only and never for the normal code flow? Or catch by reference, and not by value or a pointer? How about usefulness (or the lack or thereof) of exception specifications? Exception safety guarantees? Dark corners, such as polymorphic throwing?

From a "glass half full" point of view, with exceptions or without them C++ has always been an expert-friendly language that offers very little hand-holding to beginners. Rather than relying on the constraints of the language, a good C++ programmer turns to the community to teach him the best practices of using the language. The guidelines from the community change faster than the language itself: someone who switched from C++ to Java in 1997 often gets very confused when presented with modern C++ code - it looks different, yet the language itself has not changed much. Back to the exceptions, the C++ community learned a lot on how to effectively use them since they were first introduced to the language; someone who follows new publications and newsgroups should pick up the best practices with some effort. A good coding standard that follows the community's best practices helps a lot with the learning curve.

3.4 Exceptions are expensive and break the promise to pay only for what we use.

When talking about the performance impact of a language feature or a library, usually the best advice to a developer is to measure and decide for himself or herself whether there is any performance impact and if it is acceptable in their scenarios. Unfortunately, sometimes it gets pretty hard to follow this advice when it comes to exceptions, mostly because the performance of an exception handling mechanism is very implementation dependent; therefore, the conclusions made based on measurements with one compiler may be invalid if the code is ported to another compiler, or even another version of the same compiler. At least, the good news is that compilers are getting better in producing efficient code in the presence of exceptions and it is likely that if exceptions are not impacting the performance of your code now, they will do it even less in the future.

To understand the performance impact of exception handling in general, I strongly advise reading Chapter 5.4 of Technical Report on C++ Performance (draft). It discusses in some detail the sources of performance overhead: the code added to try-catch blocks by the compiler, impact on the regular functions, and the cost of actually throwing an exception. It also compares the two most common implementation approaches: the "code" approach where the code is added to each try-catch block, and the "table" approach where the compiler generates static tables.

After getting familiar with the performance impact scenarios of exceptions in general, the best thing to do is research the topic for your particular platform and compiler. A very nice and relatively recent presentation given by Microsoft's Kevin Frei can be found at the Northwest C++ Users Group and it covers the Visual C++ compiler for 32 and 64 bit Windows platform. Another very interesting text related to the GNU C++ compiler on Itanium is Itanium C++ ABI: Exception Handling.

Before concluding the discussion on performance and exceptions, it would be worthwhile to add that current C++ implementations do not offer predictability guarantees for exceptions, which make them unsuitable for hard real-time systems.

3.5 Exceptions are hard to introduce to legacy code.

In other words, exception safety must never be an afterthought. If the code is written without exceptions in mind, it is much better to keep exceptions out of it or completely rewrite it - there are just too many ways things can go wrong. In this aspect, it reminds me of making a code thread safe if it was originally written without thread safety in mind - just don't do it! Not only is it hard to review the code for potential problems, but more importantly, it is hard to test. How are you going to trigger all the possible scenarios that lead to exceptions being thrown?

On the other hand, if the code base is modular, it is quite possible to write new code with exceptions that would peacefully coexist with the old code. The important thing is to draw the boundaries and not let exceptions cross them.

3.6 Exceptions are easily abused for performing tasks that belong to normal program flow.

Nice aspects of using exceptions, especially the fact that they can be easily propagated from deeply nested code structures, make them tempting to be used for tasks other than error handling - most likely for some kind of poor man's continuations.

Almost always, using exceptions to affect the "normal" flow is a bad idea. As we already discussed in section 3.1, exceptions generate invisible code paths. These code paths are arguably acceptable if they get executed only in the error handling scenarios. However, if we use exceptions for any other purpose, our "normal" code execution is divided into a visible and invisible part and it makes code very hard to read, understand, and extend.

Even if we accept that exceptions are meant to be used for error handling only, sometimes it is not obvious if a code branch represents an error handling scenario or normal flow. For instance, if std::map::find() results in a value not found, is it an error or normal flow? Sometimes, an answer is easier to find if we think in terms of "exceptional", rather than "error" state. In our example, is it an exceptional case that a value is not found in a map? In general, the answer is probably "no" - there are many "normal" scenarios in which we are checking whether a value is stored in a map and branch based on the result - none of these branches is an error state. That's why map::find does not throw in case there is no requested key in the map.

How about a functionality that depends on users' input? If a user enters invalid data, is it better to throw or not? Users' input is better considered invalid unless proven otherwise and, if possible, there should be some function that validates the input before it is processed. For these validation functions, invalid data is an expected outcome, and there is no reason for it to trigger an exception. The problem is, it is often impossible to validate the input before actually processing it (parsers, for instance); in such cases, using exceptions to break the processing and report an error is often a good approach. HtmlParser from our sample 5 throws if it cannot parse broken HTML.

My personal opinion is that an exception should never be thrown if a program is working in normal conditions and with valid data. In other words, when you test a program by running it from a debugger, be sure to set a "catchpoint" that triggers whenever an exception is thrown. If the program execution breaks because an exception is thrown, that signals either a bug that should be taken care of, or a misuse of an exception handling mechanism and that should be fixed as well. Of course, an exception may be triggered due to abnormal conditions of the environment (for instance, network connection suddenly gets lost, or a font handle allocation fails) or invalid input data and in that case the exception is thrown for a good reason.

4. Conclusion

There is no simple answer to the "exceptions or error codes" question. The decision needs to be made based on a specific situation that a development team faces. Some rough guidelines may be:

  • If you have a good development process and code standards that are actually being followed, if you are writing modern-style C++ code that relies on RAII to clean up resources for you, if your code base is modular, using exceptions may be a good idea.
  • If you are working with code that was not written with exception safety in mind, if you feel there is a lack of discipline in your development team, or if you are developing hard real-time systems, you should probably not use exceptions.

License

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