1

What happens to the local variables during stack unwinding, that are referenced in exception? Consider following code:

class bar;
class my_error
{
public:
    my_error(const bar& bar) : _bar(bar) {}
    const bar& get_bar() const { return _bar; }

private:
    const bar& _bar;
}


...

bar some_local_object(...);
if (!foo()) {
   throw my_error(some_local_object);
}

...

try {
    g();
} catch (my_error& e) {
    e.get_bar()
    ...
}

What happens to some_local_object? Shouldn't it be destroyed during stack unwinding? Is it safe to use it, as provided in example?

Additional question

As already answered, this code would lead to undefined behavior. My second question to it is:

If I am neither allowed to pass reference to local object nor should I try to make copy of it, because in rare case it could cause bad_alloc (which is why, I guess, gcc standard library has no meaningful error message, i.e. map.at throws exception for which what() returns "map.at"), then what is a good strategy to pass additional information? Note that even joining multiple strings, during construction of error message could theoretically cause bad_alloc. i.e.:

void do_something(const key& k, ....)
{
    ...
    if (!foo(k)) {
        std::ostringstream os;
        os << "Key " << k << " not found"; // could throw bad_alloc
        throw std::runtime_error(os.str()); 
    }
    // another approcach
    if (!foo(k)) {
        throw key_not_found(k); // also bad, because exception could outlive k
    }
}
Sasa
  • 1,597
  • 4
  • 16
  • 33
  • 3
    This isn't really any different than if you had *returned* an object containing the reference, so no it's not safe – Ryan Haining Dec 06 '14 at 22:35

2 Answers2

4

The behavior is the same as when returning a reference to a variable on the stack: the object is destroyed before you get to use it. That is, by the time the exception is caught, the referenced object is destroyed and all access to the reference result in undefined behavior.

The relevant clause in the standard is 15.2 [except.ctor] paragraph 1:

As control passes from the point where an exception is thrown to a handler, destructors are invoked for all automatic objects constructed since the try block was entered. The automatic objects are destroyed in the reverse order of the completion of their construction.

Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380
  • Thanks. Now there is a second question to it. If I neither allowed to pass reference to local object nor should I try to make copy of it, because in rare case it could cause bad_alloc (which is why, I guess, gcc standard library has no meaningful error message, i.e. map.at throws exception for which what() returns "map.at"), then what is a good strategy to pass additional information? Note that even joining multiple strings, during construction of error message could theoretically cause bad_alloc. – Sasa Dec 06 '14 at 22:41
  • 1
    The obvious approach is to _not_ allocate memory for the extra information but embed the necessary information directly into the thrown exception object. Obviously, the amount you should put into the exception object should be fairly limited. The "Implementation quantities" don't specify a recommended minimum size of the exception object but unless you are working on an extremely resource constraint system I would expect that you can safely store, e.g., 32 bytes, i.e., a pointer to string literal with an explanation plus, at least, 3 words or 24 characters for additional information. – Dietmar Kühl Dec 06 '14 at 22:45
  • But how do you *embed the necessary information* into the thrown exception? You can't put reference to object, because even if it isn't local object, the exception could still outlive the embed object. You *shouldn't* make a copy of a object either, because of possible *out of memory* condition. I know that is extremely rare case, but nevertheless, some libraries opt for it. If you ever worked with gcc and you have std::map deep in your code, and you try map.at for key that does not exist, then you get exception which is in practical sense useless. "map.at" error msg does not tell me anything – Sasa Dec 06 '14 at 22:54
  • @Sasa: Well, you'd choose what part of the available information you'd embed, i.e., make a member of the exception object to be thrown. Clearly, you'd not just copy random objects as these could, indeed, be too big (thinking of it: you don't need to embed a pointer to the explanation as the exception type can know that information and provide it when constructing a string to be returned from `what()`). `std::map<...>` doesn't have an `at()`. ... and `std::vector<...>::at()` can easily store the index and size. – Dietmar Kühl Dec 06 '14 at 23:01
  • yes it does (http://www.cplusplus.com/reference/map/map/at/), at least since C++11. – Sasa Dec 06 '14 at 23:04
  • @Sasa: I keep forgetting that the committee likes to standardize Bad Ideas. Anyway: what it amounts to is this: you'd either accept that construction of the exception may result in a memory allocation exception or you can put only limited information into your exception object. Quite simple, actually. If you provide a move constructor for your exception class you can easily guarantee that you either throw your exception or `std::bad_alloc` is thrown while constructing the object later to be thrown (i.e. there is no issue with two exceptions). – Dietmar Kühl Dec 06 '14 at 23:16
  • @Sasa: You don't need to design exeception to be able to work in low-memory conditions. Consider that `std::runtime_error` copies a `std::string`, which uses dynamic allocation. Presumably any implementation of `std::bad_alloc` doesn't suffer from that problem, and so it's a good idea to ensure that exception handling doesn't replace `std::bad_alloc` with some other kind of exception. – Cheers and hth. - Alf Dec 06 '14 at 23:17
3

The local object is destroyed during stack unwinding. The reference then becomes invalid, a dangling reference. Which means that inspection of the exception object such that the reference is used, will have Undefined Behavior.

Cheers and hth. - Alf
  • 142,714
  • 15
  • 209
  • 331