 |
C++Talk.NET C++ language newsgroups
|
| View previous topic :: View next topic |
| Author |
Message |
ThosRTanner Guest
|
Posted: Mon Apr 18, 2005 11:03 am Post subject: Clones, exceptions and so on. |
|
|
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
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
| Back to top |
|
 |
Alberto Barbati Guest
|
Posted: Mon Apr 18, 2005 3:11 pm Post subject: Re: Clones, exceptions and so on. |
|
|
ThosRTanner wrote:
| Quote: |
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?
|
Use the Curiously Recurring Template Pattern (CRTP), like this:
---------------start code
template <class ThisClass, class BaseClass>
class Exception_ : public BaseClass
{
virtual ThisClass* clone() const
{
return new ThisClass(*static_cast<ThisClass*>(this));
}
virtual void raise() const
{
throw *static_cast<ThisClass*>(this);
}
};
// usage example:
// Exception is the base of your exception hierarchy
// instead of using:
// class ConcreteException : public Exception { ... } ;
// use this:
class ConcreteException
: public Exception_<ConcreteException, Exception>
{ ... };
// for an exception type derived from ConcreteException use this:
class MostConcreteException
: public Exception_<MostConcreteException, ConcreteException>
{ ... };
---------------end code
Although the code for raise and clone is in a base class, the
static_cast makes the compiler use the correct type.
HTH,
Alberto
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
| Back to top |
|
 |
ThosRTanner Guest
|
Posted: Tue Apr 19, 2005 9:55 pm Post subject: Re: Clones, exceptions and so on. |
|
|
Alberto Barbati wrote:
| Quote: |
Use the Curiously Recurring Template Pattern (CRTP), like this:
---------------start code
template <class ThisClass, class BaseClass
class Exception_ : public BaseClass
{
virtual ThisClass* clone() const
{
return new ThisClass(*static_cast
}
snip |
I get "invalid covariant return type" in instantiations of this. I
defined the bas class using both
class Exception : public Exception_<Exception, std::exception> { };
and
class Exception : public std::exception
{
virtual Exception *clone() const = 0;
}
but in either case, at the point of definition of a derived exception
(e.g.
class exception1: public Exception_<exception1, Exception> { }), the
compiler complains about the clone method.
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
| Back to top |
|
 |
Joshua Lehrer Guest
|
Posted: Tue Apr 19, 2005 10:04 pm Post subject: Re: Clones, exceptions and so on. |
|
|
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
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
| Back to top |
|
 |
Maxim Yegorushkin Guest
|
Posted: Wed Apr 20, 2005 6:12 am Post subject: Re: Clones, exceptions and so on. |
|
|
On 18 Apr 2005 07:03:21 -0400, ThosRTanner <ttanner2 (AT) bloomberg (DOT) net> wrote:
[]
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
cout << e->what();
if(throw_site const* s = dynamic_cast<throw_site const*>(v[i]))
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
#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__),
--
Maxim Yegorushkin
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
| Back to top |
|
 |
Alberto Barbati Guest
|
Posted: Wed Apr 20, 2005 3:51 pm Post subject: Re: Clones, exceptions and so on. |
|
|
ThosRTanner wrote:
| Quote: | Alberto Barbati wrote:
Use the Curiously Recurring Template Pattern (CRTP), like this:
---------------start code
template <class ThisClass, class BaseClass
class Exception_ : public BaseClass
{
virtual ThisClass* clone() const
{
return new ThisClass(*static_cast
}
snip
I get "invalid covariant return type" in instantiations of this. I
defined the bas class using both
class Exception : public Exception_<Exception, std::exception> { };
and
class Exception : public std::exception
{
virtual Exception *clone() const = 0;
}
but in either case, at the point of definition of a derived exception
(e.g.
class exception1: public Exception_<exception1, Exception> { }), the
compiler complains about the clone method.
|
I see. The compiler is not able to infer that ThisClass eventually
derives from Exception at the point of declaration (such detection
cannot be deferred to point of instantiation because it's part of the
member signature). It seems that I overlooked this fact. I guess we must
renounce covariant return values... this should work:
virtual Exception* clone() const
{
return new ThisClass(*static_cast<ThisClass*>(this));
}
Alberto
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
| Back to top |
|
 |
|
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum
|
|