Effective C++ by Scott Meyers (Short Summary)
  • Introduction
  • Chapter 1 - Accustoming Yourself to C++
    • Item 1 - View C++ as a federation of languages.
    • Item 2 - Prefer consts, enums, and inlines to #defines.
    • Item 3 - Use const whenever possible.
    • Item 4 - Make sure that objects are initialized before they’re used.
  • Chapter 2 - Constructors, Destructors, and Assignment Operators
    • Item 5 - Know what functions C++ silently writes a calls.
    • Item 6 - Explicitly disallow the use of compiler-generated functions you do not want.
    • Item 7 - Declare destructors virtual in polymorphic base classes.
    • Item 8 - Prevent exceptions from leaving destructors.
    • Item 9 - Never call virtual functions during construction or destruction.
    • Item 10 - Have assignment operators return a reference to *this.
    • Item 11 - Handle assignment to self in operator=.
    • Item 12 - Copy all parts of an object.
  • Chapter 3 - Resource Management
    • Item 13 - Use objects to manage resources.
    • Item 14 - Think carefully about copying behavior in resource-managing classes.
    • Item 15 - Provide access to raw resources in resource-managing classes.
    • Item 16 - Use the same form in corresponding uses of new and delete.
    • Item 17 - Store newed objects in smart pointers in standalone statements.
  • Chapter 4 - Designs and Declarations
    • Item 18 - Make interfaces easy to use correctly and hard to use incorrectly.
    • Item 19 - Treat class design as type design.
    • Item 20 - Prefer pass-by-reference-to-const to pass-by-value.
    • Item 21 - Don't try to return a reference when you must return an object.
    • Item 22 - Declare data members private.
    • Item 23 - Prefer non-member non-friend functions to member functions.
    • Item 24 - Declare non-member functions when type conversions should apply to all parameters.
    • Item 25 - Consider support for a non-throwing swap.
Powered by GitBook
On this page
  • For Example:
  • For example:
  1. Chapter 2 - Constructors, Destructors, and Assignment Operators

Item 8 - Prevent exceptions from leaving destructors.

C++ doesn't prohibit destructors from emitting exceptions, but it certainly discourage the practice. With good reason. Consider:

class Widget{
public:
    ...
    ~Widget(){...}                // assume this might emit an exception
};
void doSomething()
{
    std::vector<Widget> v;
}                                // v is automatically destroyed here.

When the vector v is destroyed, it is responsible for destroying all the Widget objects it contain. Suppose v has ten Widget objects in it, and during destruction of the first one, an exception is thrown. The other nine objects still have to be destroyed (otherwise any resources they hold would be leaked), so v should invoke their destructors. But suppose that during those calls, a second Widget object's destructor throws an exception. Now there are two simultaneously active exceptions, and that's too many for C++. Depending in the precise conditions under which such pairs of simultaneously active exceptions arise, program execution either terminates or yields undefined behavior. In this example it yields undefined behavior.

C++ does not like destructors that emit exceptions!

For Example:

Suppose, working with a class for database connections:

class DBConnection {
public:
    ...
    static DBConnection create();            // function to return DBConnection objects; params omitted for simplicity
    void close();                            // close connection; throws an exception if closing fails.
};

To ensure that clients don't forget to call close on DBConnection objects, a reasonable idea would be to create a resource-managing class for DBConnection thats calls close in its destructor.

class DBConn{                                // class to manage DBConnection objects
public:
    ...
    ~DBConn()                                // make sure database connections are always closed
    {
        db.close();
    }
private:
    DBConnection db;
};

That allows clients to program like this:

{                                            // open a block
    DBConn dbc(DBConnection::create());      // create DBConnection object and turn it over to a DBConn object to manage.
    ...                                      // use the DBConnection object via DBConn interface.
}                                            // at the end of block, the DBConn object is destroyed,
                                             // thus automatically calling close on the DBConnection object.

This is fine as long as the call to close succeeds, but if the call yields an exception, DBConn's destructor will propagate that exception, i.e., allow it to leave the destructor. That's a problem, because destructors that throw mean trouble.

There are two primary ways to avoid the trouble. DBConn's destructor could:

  • Terminate the program if close throws, typically by calling abort:

DBConn::~DBConn()
{
    try { db.close(); }
    catch(...) {
        // make a log entry that the call to close failed;
        std::abort();
    }
}

This is reasonable option if the program cannot continue to run after an error is encountered during destruction. It has the advantage that if allowing the exception to propagate from the destructor would lead to undefined behavior, this prevents that from happening. That is, calling abort may forestall undefined behavior.

  • Swallow the exception arising from the call to close:

DBConn::~DBConn()
{
    try { db.close(); }
    catch(...) {
        // make a log entry that the call to close failed;
    }
}

In general, swallowing exception is a bad idea, because it suppresses important information — something failed! Sometimes, however, swallowing exceptions is preferable to running the risk of premature program termination or undefined behavior. For this to be a viable option, the program must be able to reliably continue execution even after an error has been encountered and ignored.

A better strategy is to design DBConn’s interface so that its clients have an opportunity to react to problems that may arise.

For example:

DBConn could offer a close function itself, thus giving clients a chance to handle exceptions arising from that operation. It could also keep track of whether its DBConnection had been closed, closing it itself in the destructor if not. That would prevent a connection from leaking. If the call to close were to fail in the DBConn destructor, however, we’d be back to terminating or swallowing:

class DBConn {
public:
    ...
    void close()                        // new function for client use
    {
        db.close();
        closed = true;
    }
    ~DBConn()
    {
        if (!closed) {
            try {                       // close the connection if the client didn't
                db.close();
            }
            catch(...){                // if closing fails
                make log entry that call to close failed;
                ...                    // terminate or swallow
            }
        }
    }
private:
    DBConnection db;
    bool closed;
};

If an operation may fail by throwing an exception and there may be a need to handle that exception.

The exception has to come from some non-destructor function.

That's because destructors that emit exceptions are dangerous, always running the risk of premature program termination or undefined behavior. In this example, telling clients to call close themselves doesn’t impose a burden on them; it gives them an opportunity to deal with errors they would otherwise have no chance to react to. If they don’t find that opportunity useful (perhaps because they believe that no error will really occur), they can ignore it, relying on DBConn’s destructor to call close for them. If an error occurs at that point — if close does throw — they’re in no position to complain if DBConn swallows the exception or terminates the program.

Things to Remember:

  • Destructors should never emit exceptions. If functions called in a destructor may throw, the destructor should catch any exceptions, then swallow them or terminate the program.

  • If class clients need to be able to react to exceptions thrown during an operation, the class should provide a regular (i.e., non-destructor) function that performs the operation.

PreviousItem 7 - Declare destructors virtual in polymorphic base classes.NextItem 9 - Never call virtual functions during construction or destruction.

Last updated 3 years ago