views:

386

answers:

4

I am designing an exception hierarchy in C++ for my library. The "hierarchy" is 4 classes derived from std::runtime_error. I would like to avoid the slicing problem for the exception classes so made the copy constructors protected. But apparently gcc requires to call the copy constructor when throwing instances of them, so complains about the protected copy constructors. Visual C++ 8.0 compiles the same code fine. Are there any portable way to defuse the slicing problem for exception classes? Does the standard say anything about whether an implementation could/should require copy constructor of a class which is to be thrown?

Edit: Answering my own questions

Thanks for the answers. The two portable ways I have found to stop clients of my library from catching exceptions incorrectly by value are

  1. Throw exceptions from inside virtual raise methods of the exception classes, and make copy constructors protected. (Thanks D.Shawley)
  2. Throw derived exceptions from the library and publish exception base classes for clients to catch. The base classes could have protected copy constructors, which only allows the good way of catching them. (mentioned here for a simmilar question)

The C++ standard does state that copy constructor needs to be accessible at the point of throw. Visual C++ 8.0 in my configuration violated this part of the standard by not enforcing the presence of the copy constructor. In section 15.1.3:

A throw-expression initializes a temporary object, the type of which is determined by removing any top-level cv-qualifiers from the static type of the operand of throw and adjusting the type from “array of T” or “function returning T” to “pointer to T” or “pointer to function returning T”, respectively.

If the use of the temporary object can be eliminated without changing the meaning of the program except for the execution of constructors and destructors associated with the use of the temporary object (12.2), then the exception in the handler can be initialized directly with the argument of the throw expression. When the thrown object is a class object, and the copy constructor used to initialize the temporary copy is not accessible, the program is ill-formed (even when the temporary object could otherwise be eliminated)

+13  A: 

Your exception needs to have a public copy constructor. The compiler has to be able to copy it around for exception handling to work.

The solution to your problem is to always catch by reference instead:

try {
    // some code...
    throw MyException("lp0 is on fire!");
} catch (MyException const &ex) {
    // handle exception
}

(const-ness is optional, but I always put it in because there's rarely a need to modify the exception object.)

Thomas
In addition: always throw newly-created exceptions (or at least exceptions where you know how they were created). Don't throw via a dereferenced pointer, or a reference, which might be to a derived class.
Steve Jessop
Good point. I updated the example to give an example of a "good" throw.
Thomas
I understand that exceptions should be caught by reference. That is exactly what I wanted to enforce by hiding the copy constructor. Your answer seems to suggest there is no way for a library author to force the correct way of catching the exceptions in the client code
shojtsy
@Steve: +1, and if it is not clear enough it means that if you want to retrow an exception you should always `throw;`, rather than `throw ex;` even if `ex` is a reference as in the code above.
David Rodríguez - dribeas
+8  A: 

Thomas's answer is correct, but I'd also like to suggest you not waste your time by "designing an exception hierarchy". Designing class hierarchies is a notably bad idea, particularly when you could simply derive a couple (and no more than that) of new exception types from the C++ Standard exception classes.

anon
+1. I am with Neil here. If you think you have an exception that occurs more often than others and thus needs its own exception type (the you probably have an error situation not an exception). Otherwise 1 exception per functional unit (How you define functional unit is left vague but bigger rather than smaller).
Martin York
+3  A: 

I would steer clear of designing an exception hierarchy distinct to your library. Use the std::exception hierarchy as much as possible and always derive your exceptions from something within that hierarchy. You might want to read the exceptions portion of Marshall Cline's C++ FAQ - read FAQ 17.6, 17.9, 17.10, and 17.12 in particular.

As for "forcing users to catch by reference", I don't know of a good way of doing it. The only way that I have come up with in an hour or so of playing (it is Sunday afternoon) is based on polymorphic throwing:

class foo_exception {
public:
    explicit foo_exception(std::string msg_): m_msg(msg_) {}
    virtual ~foo_exception() {}
    virtual void raise() { throw *this; }
    virtual std::string const& msg() const { return m_msg; }
protected:
    foo_exception(foo_exception const& other): m_msg(other.m_msg) {}
private:
    std::string m_msg;
};

class bar_exception: public foo_exception {
public:
    explicit bar_exception(std::string msg_):
        foo_exception(msg_), m_error_number(errno) {}
    virtual void raise() { throw *this; }
    int error_number() const { return m_error_number; }
protected:
    bar_exception(bar_exception const& other):
        foo_exception(other), m_error_number(other.m_error_number) {}
private:
    int m_error_number;
};

The idea is to make the copy constructor protected and force users to call Class(args).raise() instead of throw Class(args). This lets you throw a polymorphicly bound exception that your users can only catch by reference. Any attempt to catch by value should be greeted with a nice compiler warning. Something like:

foo.cpp:59: error: ‘bar_exception::bar_exception(const bar_exception&)’ is protected

foo.cpp:103: error: within this context

Of course this all comes at a price since you can no longer use throw explicitly or you will be greeted with a similar compiler warning:

foo.cpp: In function ‘void h()’:

foo.cpp:31: error: ‘foo_exception::foo_exception(const foo_exception&)’ is protected

foo.cpp:93: error: within this context

foo.cpp:31: error: ‘foo_exception::foo_exception(const foo_exception&)’ is protected

foo.cpp:93: error: within this context

Overall, I would rely on coding standards and documentation stating the you should always catch by reference. Make sure that your library catches exceptions that it handles by reference and throw fresh objects (e.g., throw Class(constructorArgs) or throw;). I would expect other C++ programmers to have the same knowledge - but add a note to any documentation just to be sure.

D.Shawley
Thanks for your time! All throws in the library are anyway done through a macro to enable encoding the filename and line number into the exception, so I can just change the macro definition to call raise.
shojtsy
A: 

I'd say to not use any of the built in C++ exception code. If you must have exceptions, create your own from scratch. That's the only way to be sure they will behave similar let alone be implemented in a similar manner, and to be blunt the implementation of exceptions in C++ is incompetent.

Charles Eli Cheese