I read the following from C++ Primer (5th edition, Section 18.1.1): "When we throw an expression, the static, compile-time type of that expression determines the type of the exception object." So I tried the following code:
#include <iostream>
class Base{
public:
virtual void print(std::ostream& os){os << "Base\n";}
};
class Derived: public Base{
public:
void print(std::ostream& os){os << "Derived\n";}
};
int main(){
try{
Derived d;
Base &b = d;
b.print(std::cout); //line 1
throw b;
}
catch(Base& c){
c.print(std::cout); //line 2
}
return 0;
}
which gives me the following output:
Derived
Base
I think I understand why this output is expected: at line 1, we have dynamic binding. Now when we throw b, it is based on the static type of b, which means both the static type and the dynamic type of c is Base&, and therefore we see the result at line 2.
However, if I were to use a pointer, instead of a reference:
int main(){
try{
Derived d;
Base *b = &d;
b->print(std::cout); //line 1
throw b;
}
catch(Base* c){
c->print(std::cout); //line 2
}
return 0;
}
the output now becomes:
Derived
Derived
which seems to imply that the static type of c is Base*, but the dynamic type of c is Derived*, why? Shouldn't both the static and the dynamic types of c be Base*?
The above is entirely true. What you forget, is that pointers are objects too. And when you throw a pointer, that's your exception object.
The object you should have dynamically1 allocated is still pointed to by that
Base*
pointer. And no slicing occurs on it, because there is no attempt to copy it. As such, dynamic dispatch via-pointer accesses aDerived
object, and that object will use the overriding function.This "discrepancy" is why it is usually best to construct the exception object in the throw expression itself.
1 That pointer points to a local object, you did a big no no there and got yourself a dangling pointer.
In first case you are throwing a fresh instance of
Base
class invoking a copy constructor because you are passing a reference toBase
intothrow
operator.In second case you are throwing a pointer to a stack-allocated object of type
Derived
that goes out of scope when exception is thrown so then you capture and then dereference a dangling pointer causing Undefined Behavior.First scenario
I think that if you add some prints to your classes, you could see a clearer picture:
And the output is:
As you can see, when calling
throw b;
there is a copy construction of a different temporaryBase
object for the exception. From cppreference.com:This copy-initialization slices the object, as if you assigned
Base c = b
Second scenario
First, you are throwing a pointer to a local object, causing undefined behavior, please avoid that by all means!
Let's say you fix that, and you throw a dynamically allocated pointer, it works since you are throwing a pointer, which doesn't affect the object and preserves dynamic type information.