Implementation of catching by type

R

[rob desbois]

Consider the following code:

try
{
// throws exceptions of several types
Foo();
}
catch (out_of_range& ex)
{ }
catch (exception& ex)
{ }

Given that this requires identification of object type at runtime, how
is this implemented -- does it use RTTI? If so, then why is RTTI
attacked for its overheads given that any exception-based error
handling strategy would involve it. (I know RTTI can also point to poor
design, ignore that.)
If not, then how is this achieved?

TIA,
--rob
 
D

Dizzy

Consider the following code:

try
{
// throws exceptions of several types
Foo();
}
catch (out_of_range& ex)
{ }
catch (exception& ex)
{ }

Given that this requires identification of object type at runtime, how
is this implemented -- does it use RTTI?
Exactly.

If so, then why is RTTI
attacked for its overheads given that any exception-based error
handling strategy would involve it. (I know RTTI can also point to poor
design, ignore that.)

I don't think RTTI is evil per se, it has overhead (tiny, usually in the
form of some bytes for each polymorphic class instantiated) but so far I've
seen almost NO correct explicit RTTI usage other one exception: when I had
to implement a serialization framework to work with polymorphic types (ex.
when deserializing on a base pointer the framework should be able to
realise what type exactly has been stored, create it and so on) in which
case is very clear why I had to use RTTI (because you just need the type
information of a base pointer/reference). I guess in this exception too can
be workarround with having the user specify the type behind the base
pointer/reference when serializing but seems awkward to me that way.
If not, then how is this achieved?

AFAIK as I said it uses RTTI :)
 
R

Risto Lankinen

Dizzy said:

Yes, except not exactly. Try this:

- - -

#include <iostream.h>

class Base
{
public:
virtual ~Base()
{
// Base and its derivants are now RTTI-enabled - if needed...
}
};

class Super : public Base
{
};

int main()
{
Base *p;
try
{
p = new Super();
throw p;
}
catch( Super * )
{
cout << "Super" << endl;
}
catch( Base * )
{
cout << "Base" << endl;
}
delete p;
return 0;
}

- - -

So, if RTTI (run-time type information) were used to identify the
appropriate exception handler, this progam should print "Super".
However, the handler is determined in the compile-time, based
on the static type of the expression used in the throw-statement;
hence this program prints "Base".

To the original poster: All catch-blocks will get an entry point
in the object file. The entry points are categorized based on
the type of an object they catch. When a throw-statement is
compiled, the compiler emits code that scans the catch handler
of the category determined by the type of the expression in the
throw-statement: If you throw a Base *p [like in my example]
the catch handlers for a "Base *" are scanned. It does require
linker magic, but RTTI is definitely not necessary [*].

- Risto -

[*] Individual compiler implementors, however, are free to use
RTTI type_info structures in the exception implementation if they
deem it useful (and some indeed do).
 
R

Ron Natalie

Risto said:
So, if RTTI (run-time type information) were used to identify the
appropriate exception handler, this progam should print "Super".
However, the handler is determined in the compile-time, based
on the static type of the expression used in the throw-statement;
hence this program prints "Base".

It is true that the type of the object (which is Base*) is used.
The VALUE of the object plays no roll in the selection of the
matching catch block. You can throw null pointers.
Dynamic typing is a feature of the pointed
to value.

It is NOT THE CASE that this can be determined at COMPILE time.
Imagine the following:

void Thrower() {
int i;
cin >> i;
switch(i) {
case 1:
throw (Base*) 0;
case 2:
throw (Super*) 0;
default:
throw (int) 0;
}
}

int main() {
try {
Thrower();
} catch (Super*) { ...
catch (Base*) { ...
 
M

mlimber

Risto said:
[...] Try this:

- - -

#include <iostream.h>

class Base
{
public:
virtual ~Base()
{
// Base and its derivants are now RTTI-enabled - if needed...
}
};

class Super : public Base
{
};

int main()
{
Base *p;
try
{
p = new Super();
throw p;
}
catch( Super * )
{
cout << "Super" << endl;
}
catch( Base * )
{
cout << "Base" << endl;
}
delete p;
return 0;
}

Stylistic note: FAQ 17.7 rightly says that unless there is a good
reason not to, one should catch by reference rather than pointer as you
do here. I'd combine that with FAQ 18.1 to say that one should catch by
const reference unless there's a good reason not to.

Cheers! --M
 
R

Risto Lankinen

Ron Natalie said:
It is NOT THE CASE that this can be determined at COMPILE time.

And yet, it is the case. Let's reorder a bit and, hmm, compile...
int main() {
__main:

try {
Thrower();
} catch (Super*) { ...

..DATA SEGMENT __pSuper_handlers
DW __main
DW __pSuper_handler$main$filename = __pSuper_handler
DW __main_unwrap
..CODE
__pSuper_handler:
; code for the catch handler
catch (Base*) { ...

..DATA SEGMENT __pBase_handlers
DW __main
DW __pSuper_handler$main$filename = __pSuper_handler
DW __main_unwrap
..CODE
__pSuper_handler:
; code for the catch handler

. . .

..CODE
__main_unwrap:
; code to unwrap this function call level if no handler found

void Thrower() {
int i;
cin >> i;
switch(i) {
case 1: throw (Base*) 0;

1. locate [immediate/next] caller's stack frame
2. within data segment named __pBase_handlers...
3. is current return address between __XXX and __XXX_unwrap?
* No, call __XXX_unwrap and go back to 1
* Yes, jump to __pSuper_handler$XXX$filename
case 2:
throw (Super*) 0;

Likewise here, except that two tables (pBase and pSuper) need
to be scanned, because Super is a derived class [however, even
this can be determined thru static analysis]

- - -

Linker magic I mentioned in my original article is twofold:

1. Unwrappers are generated everywhere where throwers are
located. There may well be more than one thrower for any given
type, but the linker should not complain about them having the
same name in the name table (and instead combine these into one
function only).

2. Data segments containing pointers to catch handlers may come
from multiple compilation units; linker must be able to consolidate
these tables so that the scanner routine finds all handlers.

Feel free to ask if my [hastily written] description is insufficient.

Cheers!

- Risto -
 

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
474,432
Messages
2,571,680
Members
48,796
Latest member
Greg L.

Latest Threads

Top