Clones, exceptions and so on.

T

ThosRTanner

Having had a look at ways of passing exceptions from one thread to
another, whilst trying to keep the "catch by const ref" coding standard
alive, I've ended up making a class from which exceptions thrown in the
1st thread should inherit from, which has 3 virtual functions (clone,
which appears to be pretty standard, raise (just does throw *this), and
another), which are all utterly trival. However, these 3 trivial
functions have to be implemented in every derived class, or bad things
will happen.

OK, I can ensure that the functions are implemented in every
immediately derived class by making them pure virtual, but if a class
is 2 levels away, how do I ensure that that class implements these
funcions as well?

The other issue is writing 3 functions that are almost line for line
copies of what was written last time - this almost calls for either a
macro or some sort of template. However, I don't think templates quite
do what I want (especially as the raise() function does "throw *this"
so will probably end up throwing the wrong exception if the real class
inheris from a template).

If I don't provide some sort of functionality that makes this trivially
easy, derived classes could result in nasty errors with exceptions
being sliced.

Any ideas?

Thanks
 
J

Joshua Lehrer

Your problem is that "throw" is based on the static-type at the point of
throw, not the dynamic type.

If you want to throw based on the dynamic type, getting e->Throw() to work,
you must be able to get the dynamic type, and thus, you must use virtual
functions.

However, this is a great place to use the non-virtual interface pattern.
"Throw" should be a public, non-virtual method on the base class.
"ThrowImpl" should be a private, pure-virtual method on the base class. The
public "Throw" calls the private "ThrowImpl". As you have pointed out, a
pure virtual function guarantees a concrete class, but not necessarily that
the bottom-most class has overriden the method. In debug mode, you can
ensure that the bottom-most subclass has overridden "ThrowImpl".

Here is an example. Throw sets up a try/catch block to catch the base
exception by reference. It then calls the pure virtual method. If it fails
to catch an exception, it terminates. If it does catch the exception, it
asserts that the type is correct, then re-throws it using "throw":

struct Exception {
void Throw() const {
try {
ThrowImpl();
} catch (const Exception &f) {
assert((typeid(*this)==typeid(f)) && "Invalid override of ThrowImpl");
throw;
}
assert(!"Invalid override of ThrowImpl");
terminate();
}
private:
virtual void ThrowImpl() const = 0;
};



Here is a second, more complicated example. In this example, I store a
function that will be used to throw. This function pointer is stored in a
virtual base class, thus ensuring that the bottom-most sublcass properly
installs a correctly-typed function pointer. This virtual base class is
called "ExceptionBase". In order to make things easier on the consumer
"Good", the real base class "Exception" supplies some templated helper
methods to help "Good" intialize "ExceptionBase".

Simply put, there are no virtual functions. The bottom most class is
required to pass the "this" pointer and a type-safe function pointer to the
virtual base class. When throwing, the information stored in the virtual
base class is used to throw the proper exception.

The benefits of this approach over the previous one is that it is enforeced,
AT COMPILE TIME, that all subclasses are written correctly. Further, there
is nothing for a subclass to do except intialize a base class, and they all
do it in the same way. They don't need to write a "Throw" method. Notice
how simple "Good" is, and how "Bad" fails to compile.

The downside is that this is really complicated and hard to follow and
maintain in the future.


//this will be the virtual base class. It stores the "this" pointer of the
ultimate sublcass
//as well as a pointer to a function used to "Throw"
struct ExceptionBase {
protected:
ExceptionBase(std::pair<void*,void(*)(void*)> p) : m_that(p.first),
m_func(p.second) {
}
void * const m_that;
void (* const m_func)(void*);
};


struct Exception : protected virtual ExceptionBase {
public:
//to throw the exception, call this method. It gets dispatched
//through the function pointer.
void Throw() const {
(*m_func)(m_that);
}

protected:

//helper method used by sublcasses to create the parameters for the
ExceptionBase class
template <typename T>
static inline std::pair<void*,void(*)(void*)> make_throw(T* that) {
return
std::pair<void*,void(*)(void*)>(that,static_cast<void(*)(void*)>(Throw<T>));
}

//required, never used
Exception() : ExceptionBase(make_throw(this)) { }

private:

//helper method used by make_throw
template <typename T> static inline void Throw(void* that) {
throw *(static_cast<T*>(that));
}
};

struct Good : public Exception {
Good() : ExceptionBase(make_throw(this)) {
}
};

struct Bad : public Good {
//fails to compile!
};


joshua lehrer
factset research systems
NYSE:FDS
 
M

Maxim Yegorushkin

[]
Any ideas?

Here is a tiny framework for you for making all thrown exceptions clonable
and embedding throw site information in exceptions without writing a
single line of code - just include the framework header in all your *.cpp
files.

Usage:

int f()
{
throw std::logic_error("enough!");
}

int g(std::vector<clonable_exception*>* v)
{
for(int n = 10; n--;)
{
try { n = f(); }
catch(clonable_exception const& e)
{
v->push_back(e.clone());
}
}
}

int main()
{
using namespace std;
vector<clonable_exception*> v;
g(&v);
for(size_t i = 0; i < v.size(); ++i)
{
if(exception const* e = dynamic_cast<exception const*>(v))
cout << e->what();
if(throw_site const* s = dynamic_cast<throw_site const*>(v))
cout << " @ " << s->file << ':' << s->line;
cout << endl;
}
}

Output:
[max@my exp]$ ./exp
enough! @ exp.cpp:81
enough! @ exp.cpp:81
enough! @ exp.cpp:81
enough! @ exp.cpp:81
enough! @ exp.cpp:81
enough! @ exp.cpp:81
enough! @ exp.cpp:81
enough! @ exp.cpp:81
enough! @ exp.cpp:81
enough! @ exp.cpp:81

The framework implementation:

#include <iostream>
#include <stdexcept>
#include <vector>

struct throw_site
{
throw_site(char const* file, int line) : file(file), line(line) {}
char const* file;
int line;
};

struct clonable_exception
{
virtual clonable_exception* clone() const = 0;
virtual void rethrow() const = 0;
};

template<class E>
void operator,(throw_site const& site, E const& e)
{
struct clonable_exception_impl : E, throw_site, clonable_exception
{
clonable_exception_impl(E const& e, throw_site const& site) :
E(e), throw_site(site) {}
clonable_exception_impl* clone() const { return new
clonable_exception_impl(*this); }
void rethrow() const { throw *this; }
};

throw clonable_exception_impl(e, site);
}

#define throw throw_site(__FILE__, __LINE__),
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,744
Messages
2,569,484
Members
44,904
Latest member
HealthyVisionsCBDPrice

Latest Threads

Top