The new
expression in the try
block throws a bad_alloc
exception in my computer.
Note that the catch clause receives an exception object by value, not by reference. How come e.what()
prints "bad allocation"
? I thought it would be sliced.
#include <iostream>
int main()
{
try
{
int* p = new int[0x1F000000];
}
catch(std::exception e)
{
std::cout << e.what() << std::endl;
}
}
Visual Studio (Dinkumware?) uses an implementation of std::exception
that contains internal storage† for the message. (Complete with a non-standard constructor that accepts a string.)
Because of this, no virtual dispatch is actually needed to get the error message, it survives any slicing.
A more orthodox implementation would indeed print a generic exception message, because the derived object was sliced off. (Effectively, MS has made std::exception
and std::runtime_error
equivalent. There's nothing wrong with this, since the return value of std::exception::what
is implementation-defined, but it explains your results.)
†Internal storage here is used loosely. It doesn't have an internal buffer, but it has a const char*
and a bool
. The const char*
points to the message (the return value of what()
), and the bool
is a flag determining if the buffer should be deleted.
It's like this:
class msvc_exception // for exposition
{
public:
msvc_exception(const char* msg) :
mMsg(msg),
mDoDelete(false)
{}
msvc_exception(const std::string& msg) :
mMsg(copy_string(msg)),
mDoDelete(true)
{}
virtual ~msvc_exception()
{
if (mDoDelete)
delete [] mMsg;
}
virtual const char* what() const throw()
{
return mMsg ? mMsg : "unknown";
}
private:
const char* copy_string(const std::string& str)
{
const char* result = new char[str.size() + 1];
std::copy(str.begin(), str.end(), result);
result[str.size()] = 0; // null-terminate
return result;
}
};
You see now that bad_alloc
works like this:
class msvc_bad_alloc : // for exposition
public msvc_exception
{
public:
msvc_bad_alloc() :
msvc_exception("bad_alloc") // note: a static string, no dynamic storage
{}
};
Slicing doesn't affect the message because the message "exists" in the base class.
Other compilers, like GCC and LLVM, implement it a bit more straight-forwardly:
class orthodox_exception
{
public:
orthodox_exception(){}
virtual ~orthodox_exception() {}
virtual const char* what() const throw()
{
return "orthodox_exception";
}
};
class orthodox_bad_alloc :
public orthodox_exception
{
public:
const char* what() const throw()
{
return "orthodox_bad_alloc";
}
};
Here, slicing would affect your outcome. (That said, after all this: always catch by reference.)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With