I want to throw my own exceptions with the base class Exception
. There is a virtual method print
which will be overwritten by the subclasses. I only catch the type Exception&
and use print
to get the specific error. The problem is that once I throw a reference of a subclass it is trated as if it were the base class.
Here is an example:
#include <iostream>
using namespace std;
class Exception
{
public:
virtual void print()
{
cout << "Exception" << endl;
}
};
class IllegalArgumentException : public Exception
{
public:
virtual void print()
{
cout << "IllegalArgumentException" << endl;
}
};
int main(int argc, char **argv)
{
try
{
IllegalArgumentException i;
Exception& ref = i;
cout << "ref.print: ";
ref.print();
throw ref;
}
catch(Exception& e)
{
cout << "catched: ";
e.print();
}
}
The output of this example is:
ref.print: IllegalArgumentException
catched: Exception
Using a reference should result in the print
method from the derived class being used. Inside the try block the reference does use it. Why doesn't the catched Exception&
act like an IllegalArgumentException
and how can I get that behavior?
The following code seems to do what it is supposed to:
try
{
IllegalArgumentException i;
Exception* pointer = &i;
throw pointer;
}
catch(Exception* e)
{
cout << "pointer catched: ";
e->print();
}
but doesn't the pointer become possibly invalid outside the scope of the try block? It would then be risky to do this and if I allocate memory on the heap to get around that problem I have the responsibility for the deletion inside the catch block which isn't pretty either. So how would you solve the problem?
When you throw an object a copy of that object is made to some other location so stack unwinding can proceed and the copy can be passed to the exception handler (either by reference so no further copies are made, or by value creating a second copy).
You can verify that a copy is made if you make your exception non-copyable. You will no longer be able to throw objects of that type.
When the implementation copies the object you throw, it looks at the static type of the expression, not the dynamic type. This means that in your code, it sees you're throwing an
Exception
, and so the resulting copy is an example of slicing (i.e. instead of copying the complete object, only a base class sub-object is copied).You can avoid the slicing if you ensure that the static type of the throw expression matches the type of the complete object, which you can do by simply not forcing the type to
Exception
.This should print "caught: IllegalArgumentException".
throw
implicitly copies, and consequently slices. Quoting C++11, §15.1/3:I've seen a handful of codebases that work around this by throwing pointers to exceptions rather than objects directly, but personally I'd just reconsider your "need" to do this in the first place.
What you see is called slicing.
You are apparently used to polymorphism, where you can assign a pointer to a subclass to a pointer to a base class, and you retain the (sub)type and all data. After all, it's been just a pointer being copied, not the object itself. However, nothing like this can happen when the assignment is directly between the objects themselves; the base class typically has shorter instances, and there may be no available room (in your case, on the stack; in somebody else's case, on the heap) following the variable of the base class type.
So, C++ is defined to perform slicing on the object. Only the base class part is copied, and the type is "downgraded" to the base class as well.
You can use a pointer instead of reference to avoid slicing:
The caught pointer points to the same object, because the copy constructor is not called implicitly.