Herb Sutter (http://www.gotw.ca/) chairs the ISO C++ Standards committee and is an architect in Microsoft's Developer Division. His most recent books are Exceptional C++ Style and C++ Coding Standards (Addison-Wesley). Jim Hyslop is a senior software designer for Leitch Technology International. He can be reached at jhyslop@ ieee.org.
Frack!" I muttered. "Cylons gobbling your code again, pardner?" Wendy muttered back, over the cubicle wall.
"Ah, well, sorta," I sat back and stretched. "Got a second?"
"Sure," Wendy plunked herself in my guest chair. "'Sup?"
"Well, I'm trying to design this library," I tapped on the screen, "so that if it needs to throw an exception, the library users can pass in the type of exception they want thrown. All the exceptions are derived from a common base class. Problem is, the only exception I catch is the base class."
"Hmmm..." Wendy leaned in to examine the code.
#include <iostream> #include <exception> using namespace std; class MyExceptionBase : public exception { }; class MyExceptionDerived : public MyExceptionBase { }; void throwit( MyExceptionBase & e ) { throw e; } void dothrow( MyExceptionBase & e ) { try { throwit( e ); } catch( MyExceptionDerived & me ) { cout << "Caught derived\n"; } catch ( MyExceptionBase & me ) { cout << "Caught base\n"; } } int main() { MyExceptionBase base; MyExceptionDerived derived; dothrow( base ); dothrow( derived ); }
"Well, let's see what happens if you move the logic from the dothrow function into main," Wendy said as she adjusted the code:
int main() { MyExceptionBase base; MyExceptionDerived derived; try { throwit( base ); } catch( MyExceptionDerived & me ) { cout << "Caught derived\n"; } catch ( MyExceptionBase & me ) { cout << "Caught base\n"; } try { throwit( derived ); } catch( MyExceptionDerived & me ) { cout << "Caught derived\n"; } catch ( MyExceptionBase & me ) { cout << "Caught base\n"; } }
She compiled and ran the code. The output was the same: "Caught base" times two.
"Didn't help," I volunteered. "Aw, let's see what the Standard says," I slid the keyboard over and called up the Holy...er, that is, the Standard. "Ah, here it is:"
A throw-expression initializes a temporary object, called the exception object, the type of which is determined by removing any top-level cv-qualifiers from the static type of the operand of throw. [1]
"Static type!?!? Frack!" I repeated. "No wonder it's not working." I sighed. "Okay, now what?" Wendy and I stared at the code.
"Delegate," a voice whispered in my ear. I jumped, half expecting to see Tricia Helfer standing there. It was the Guru. I was just getting used to the Guru snapping a book to get our attention. Now she was changing her tactics!
"Gah...er, I mean, delegate?"
"Yes, my child," the Guru straightened up, as Wendy returned to her cubicle. "It is clear that your throwit function cannot pass the dynamic type of the object to the throw expression. But there is something in throwit that knows the dynamic type, and can therefore pass it to the throw expression."
"Oh!" I exclaimed. "Sure, easy enough. I'll just put in a series of if/else clauses based on its typeid, and use a dynamic_cast on it to cast it to the proper type. Like this," I quickly modified the function:
void throwit( MyExceptionBase & e ) { if ( typeid( e ) == typeid( MyExceptionDerived ) ) { throw dynamic_cast< MyExceptionDerived &>( e ); } throw e; } "That oughta do it," I fired up the compiler, then ran the program: Caught base Caught derived
"Yipee, that's got it, thanks." I looked up at the Guru. She did not share my enthusiasm.
"My apprentice, you seem to have forgotten something. When you take action based on the type of an object, that means..."
"Ummm...oh, right. That's precisely why polymorphism was inven...oh, I get it now. I need a virtual function in MyExceptionBase." I backed out my changes, and modified the classes:
class MyExceptionBase : public exception { public: virtual void Throw() { throw *this; } }; class MyExceptionDerived : public MyExceptionBase { public: virtual void Throw() { throw *this; } }; void throwit( MyExceptionBase & e ) { e.Throw(); }
"Much better, my young apprentice," the Guru smiled. "And yet, is there another way you can have the user set the rules?"
"Rules?" I asked.
"As in policies," Wendy prompted over the cubicle wall.
"Policies?" I asked.
"Yes, my apprentice," the Guru replied, "can this be done using a policy instead?"
"Uh, I think so," I turned back to the keyboard.
class MyException1 : public exception { public: void Throw() { throw *this; } }; class MyException2 : public exception { public: void Throw() { throw *this; } }; template < class ExceptionType > void throwit( ExceptionType & e ) { e.Throw(); } template< class ExceptionType > void dothrow( ExceptionType & e ) { [... same implementation as before]
I compiled and ran the program. It worked perfectly. "Kewl," I said. "Now I don't have to force everybody to derive their class from my own."
"Yes, my child, policy-based programming provides you with much flexibility and power." She turned and glided away.
References and Further Reading
- [1] ISO/IEC 14882:2003(E), "Programming LanguagesC++," 15.1 63 and "How do I throw polymorphically?" C++ FAQ Lite, http://www.parashift.com/c++-faq-lite/exceptions.html#faq-17.10.