How to return a string from what() properly?

Apr 29, 2017 at 7:18pm
Hello everyone!

I'm dealing with a very weird issue. I have created a program that uses exceptions heavily to report errors to the user. Most exceptions come with a long error message, but to my surprise, very long messages are not returned properly by the "what()" method.

After some trial and error with smaller programs where the issue happens with way shorter strings too, I came to the conclusion that there might be an issue with local variables in the what method. Currently, the simplest method I used looks like this:

1
2
3
4
5
const char* Error::what() const noexcept {
  std::stringstream s;
  s << "Error: " << err::ErrType::msgs[errorcode] << "\n" << message;
  return s.str().c_str();
}


I'm pretty sure, the problem is that the variables are no longer valid once the method returns.
Is there any proper way to fix this issue, other than storing everything directly in a variable in the exception object because that would be very annoying for the more complicated objects.
Last edited on Apr 29, 2017 at 7:19pm
Apr 29, 2017 at 7:27pm
Just return the std::string

1
2
3
4
5
std::string Error::what() const noexcept {
  std::stringstream s;
  s << "Error: " << err::ErrType::msgs[errorcode] << "\n" << message;
  return s.str();
}
Apr 29, 2017 at 7:33pm
But as far as I'm concerned, std::exception (which is the superclass) requires what to return a C string, or am I wrong?
Apr 29, 2017 at 8:56pm
Yes, but it wasn't clear that this inherited from std::exception.

Regardless, I wasn't paying attention. If s.str() throws, your program crashes - so you need to handle that exception if it arises.

You may consider using e.g., a static message buffer or a shared std::string to build the contents of the message. Note that copying a std::exception must not throw: make sure any data contained in the class is safely copyable. This has the side effect of forcing you to build the message earlier, so the exception object can carry data with reference semantics instead.

Does std::runtime_error suit your needs? Try something roughly like the following.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# include <stdexcept>
# include <iostream>

static std::string const msgs[] { "ENOENT: no such file or directory" /* ... */ }; 

struct Error: public std::runtime_error { 
  Error(int error_code, std::string const& msg) // may throw std::bad_alloc
    : std::runtime_error(std::string{"Error: "} + msgs[error_code] + ": " + msg) 
    , error_code{error_code}
  {}
  
  int const error_code;  
}; 

int foo() noexcept(false) { 
    std::cout << "called foo" << std::endl;
    throw Error{0, "whatever.txt"};
    std::cout << "didn't make it :(\n";
}

int main() {
    try {
        foo();
    } catch (Error const e) {
        std::cerr << e.what() << '\n'; 
    }
}


http://coliru.stacked-crooked.com/a/d3d0fc115f9c23a0
Last edited on Apr 29, 2017 at 9:03pm
Apr 29, 2017 at 9:04pm
This looks really good, I think I'll go with that. Thanks a lot!
Topic archived. No new replies allowed.