views:

199

answers:

4

So, I am try to write a simple base Exception class for C++, based on the Java Exception class. I'm sure there are great libraries out there already, but I am doing this for practice, not production code, and I'm curious and always looking to learn. One of the things the Java's Exception does, which I would like to also implement, is the concept of a 'cause'. In Java, a new Exception with a cause looks like:

Exception cause = new Exception();
Exception newExcept = new Exception(cause);

However, in C++, passing an Exception as an argument to the constructor is how the copy constructor is called. So, there's the conceptual disconnect between copying the Exception and creating a new Exception with a cause. This isn't a problem in Java, obviously.

I guess I'm just wondering what the best way to handle this would be. A few ideas I had were:

  • Differentiate with a dummy variable
  • Just create new Exception, and called setCause() method
  • Something like copy constructor is Exception(Exception &) and constructor with cause is Exception(Exception *)

Thanks

+2  A: 

You could use the factory model:

Exception cause = Exception.Create();
Exception newExcept = Exception.Create( Exception cause );
yhw
I actually really like this solution too. It's quite elegant and lateral, but my chosen answer is probably a little close to what I was looking for.
Roadrunner-EX
+1  A: 

Just add the string of the cause exception to the current exception:

try
{
    throw std::runtime_error("Failed to work");
}
catch(std::exception const& e)
{
    // New exception (add origianl exception text).
    throw std::runtime_error(std::string("We were doing poobar when: ") + e.what());
}
Martin York
Just to be picky that does not look like a correct carch statement, however it does look like a good catch statement.
HandyGandy
A: 

You're question is unclear, and doesn't even seem to be Java properly but a Java idiom which is supported by a library. I am guessing the idiom you describe is passing the original exception as an argument to an exception you create when rethrowing.

The solution is to create a library_exception ( or whatever you want to call it )

class library_exception: public std::exception
{
 ...
 public:
   library_exception(const  std::exception &e)
 ...
}
...
catch(const std::exception &e)
{
  ...
  throw library_exception(e);
}

Different classes no copy constructor called.

HandyGandy
+4  A: 

The exception - when allocated on the stack (I would strongly recomend this) - is freed after the catch clause. So you need to create a copy of the "inner" exception in the newly created exception. If you catch a base class of your exception it will loose it's correct type unless you give your exception a clone method.

#include <string>
#include <exception>

class MyBaseException : public std::exception
{
public:
    MyBaseException(const std::string& what = std::string("MyBaseException"))
        : m_BaseException(0), m_What(what) {}  //Constructor without inner exception

    MyBaseException(const MyBaseException& innerException, const std::string& what = std::string("MyBaseException"))
        : m_BaseException(innerException.clone()), m_What(what) {}  //Constructor with inner exception

    template <class T>  // valid for all subclasses of std::exception
    MyBaseException(const T& innerException, const std::string& what = std::string("MyBaseException"))
        : m_BaseException(new T(innerException)), m_What(what) {}

    virtual ~MyBaseException() throw()
        { if(m_BaseException) { delete m_BaseException; } } //don't forget to free the copy of the inner exception
    const std::exception* base_exception() { return m_BaseException; }
    virtual const char* what() const throw()
        { return m_What.c_str(); } //add formated output for your inner exception here
private:
    const std::exception* m_BaseException;
    const std::string m_What;
    virtual const std::exception* clone() const
        { return new MyBaseException(); } // do what ever is necesary to copy yourselve
};

int main(int argc, char *argv[])
{
    try {
        try {
            throw std::exception();
        }
        catch(const std::exception& e) {
            throw MyBaseException(e, "bad");
        }
    }
    catch (const MyBaseException& e) {
        throw MyBaseException(e, "even worse");
    }
    //throw MyBaseException(1, "will not compile");
}
David Feurle
IIRC thrown exceptions aren't allocated on the stack, but in a "special" memory area used just for holding thrown exceptions, since when an exception is propagating the stack is unwound.
Matteo Italia
@matteo Probably you are right. They are "special" since stack unwinding has already happened when the catch block is called. Probably this is compiler specific.
David Feurle
You need to use a smart pointer to manager m_BaseException as exception objects get copied all over the place. Currently you are potentially going to delete your base exception pointer (and recursively its chain) multiple times.
Martin York
@martin thought about adding it - but avoided it to minimize external deps. Should have better added it though :P
David Feurle
If you don't add a smart pointer then you have two options: 1) Remove the delete in the destructor and leak (PS you don't need to test for NULL before delete). 2) Add Copy constructor and assignment operator to cope with managing the exceptions (each new exception object will need to create its own copy of m_BaseException (to prevent multiple deletes) and manage its release explicitly). 3) Use a smart pointer from std:: (there is no dependency here as all systems must have it). Embedded devices have limited resources so will not use chained exceptions so you are guaranteed to have STL.
Martin York
@Martin I need to test for 0 - you can create a MyBaseException without an inner exception (first constructor). I read about auto_ptr from std. More or less it's broken since it does not work correctly with a copy (copies will be 0?). I thought about using boost shared_ptr but this would add the external dep.
David Feurle
1) You still don't need to test for NULL (0) it is perfectly valid for you to call delete on a NULL (0) pointer.
Martin York
2) Auto_ptr<> is not broken it works perfectly. It uses move semantics; though normally not what you need they could definitely be used in this context as what you need is move. But if you decide they are not, then you need to do some extra work and thus di the management yourself. **BUT** using auto_ptr will make the current code better as it will not leak and it will not double delete (which your code does (as it is broken)).
Martin York
@martin what makes you think it will call double delete? it allocates the memory in the constructor and frees in the destructor. if it would double free it would mean it calls the constructor once and the desctructor twice. why should this happen?
David Feurle
@David Feurle: Also 'new T(innerException)' is going to cause problems with unexpected slicing (when you least expect it). Not fatal but probably not what you want or need.
Martin York
@David Feurle: Also the clone() looks like a good idea (as it works it Java). But in C++ it is not so useful and is rarely used.
Martin York
@martin I don't see any slicing issue. There is no reference to base used. Pointers are assigned by copying the adress - no copy constructor called at all. Some type information is lost (since you can catch a base class). I tried MyBaseException tmp; tmp = e; and it didn't call a double delete eventhough it does not work as intended.
David Feurle
@martin I used the clone method to loose as least type information as possible. The instance can copy itself without loosing type information even if you call the function via a pointer to base.
David Feurle
Martin York