2

I noticed the following behavior by accident (missed catching by reference), but I couldn't find information that, if I knew it before hand, would have allowed me to predict it.

With the minimal example

#include <iostream>
#include <stdexcept>


int main()
{
    try
    {
        // Added this and the next line to check that the exception 
        // created has already the what() set to the input string.
        std::out_of_range d("Out of range exception"); 
        std::cout << "d.what() = " << d.what() << std::endl;
        throw d;
    }
    catch (std::exception e) // Captured by value
    {
        std::cout << e.what() << std::endl;
    }

}

If I compile it with g++ -std=c++17 and with Visual C++ I get different behaviors. With the first it prints d.what() = Out of range exception\nstd::exception, while the second it prints d.what() = Out of range exception\nOut of range exception.

In principle there could be slicing when the std::out_of_range is captured by value and converted to the std::exception type. This means that I could expect not getting the same behavior as an object from std::out_of_range object when printing its what().

Question: The part that I don't know how to explain is getting different behaviors for the two compilers. Is this because this slicing is undefined behavior in the C++ standarization, or is it that one of these two compilers is not complying with it?

Extra observation: I just noticed that in this link there is no mention of the class std::exception having a constructor that inputs a const char* const &, while in the Microsoft website they include it. My accidental example shows that they indeed implemented these classes differently. My question is still whether they were allowed (if this behavior is undefined) or if one of them is not complying and which one.

user647486
  • 187
  • 5
  • 1
    The issue isn't the slicing, IMO. It is when and where the `what()` is set. Is it set after the slicing occurs or before the slicing occurs? That is probably what should be investigated. – PaulMcKenzie Mar 08 '19 at 17:48
  • @PaulMcKenzie As far as I understand (which is limited) and object of type `std::out_of_range` is created in the line with the `throw`. (One of)The constructor(s) of this class inputs the string to be the value of output by `what()`. Then during the `catch` this object is assigned to `e`, which is of type `std::exception`, a parent class of `std::out_of_range. Doesn't this mean that the slicing happens after? Let me do some experiments anyway. – user647486 Mar 08 '19 at 18:41
  • [Try this answer](https://stackoverflow.com/questions/1095225/exception-slicing-is-this-due-to-generated-copy-constructor) – PaulMcKenzie Mar 08 '19 at 18:44
  • @PaulMcKenzie I did a small change to the example. I created the `std::out_of_range` with its `what()` string, printed it before throwing. The message printed is the one passed to its constructor. However, after catching the slicing changed the message, when compiling with g++. With VisualC++ it prints the same message both times. – user647486 Mar 08 '19 at 18:47
  • @PaulMcKenzie I looked at the question and answer that you linked to. I understand why the exception thrown doesn't have to behave as the exception catch-ed in my code. They have different types. The part that I don't understand is the difference in behavior between the two compilers. – user647486 Mar 08 '19 at 18:55
  • Why do you catch the exception *by value* in the first place? With an exception hierarchy, you are supposed to catch by reference (for std::exception) or by pointer (some frameworks that dynamically allocate exceptions), which totally avoids such issues. – zett42 Mar 08 '19 at 19:23
  • 1
    @zett42 In the question it is said why: By accident. But well, a happy accident because it let me to learn something I had not seen. – user647486 Mar 08 '19 at 19:24
  • On a side note: there are free static analyzers that can check for this kind of slicing – JVApen Mar 08 '19 at 19:58

1 Answers1

1

The object is still being sliced; you can use typeid( e ).name() to print out the actual type and it shows as std::exception. As you found, MSVC implements what() to return a pointer to a string that is set at std::exception construction time, so it's not lost when the out_of_range exception is sliced back into the base exception.

Per https://en.cppreference.com/w/cpp/error/exception/exception, what() "returns an implementation-defined string" so MSVC is free to do it this way.

To print the type, add this to your catch block:

std::cout << "e.what() = " << e.what() << " Actual type = " << typeid( e ).name() << std::endl;

HerrJoebob
  • 2,264
  • 16
  • 25