1

I'd like to have a custom exception that has embedded contextual information, including strings, and a formatted what().

e.g.:

struct MyError : public std::runtime_error {
  MyError(std::size_t line, std::size_t column, std::string expected, std::string found) 
    : std::runtime_error(FormatPrettyMessage(...)), line(line), column(column), expected(expected), found(found) {}
  MyError(const MyError&) = default;
  MyError& operator =(const MyError&) = default;
  ~MyError() = default;

  size_t line;
  size_t column;
  std::string expected; // these are
  std::string found;    // problematic
};

Best practices, though, say an exception should have a noexcept copy constructor, because throwing might involve implicit copying.

The above can't have a noexcept copy constructor, as copying a string can throw std::badalloc. cppreference hints that standard libraries use a copy-on-write string to avoid this (libstdc++ indeed uses an internal COW string implementation).

But how should a layperson idiomatically add a string member to their custom exception? Trying to serialize my data and stuff it into the runtime_error message, as this answer recommends, feels hacky and forces the exception handling code to parse and reformat the what() output. But throwing a whole custom string into my small project just to make an exception pretty feels excessive.

parktomatomi
  • 3,851
  • 1
  • 14
  • 18
  • `std::exception` and derived classes aren't required to store a `std::string`. To avoid exceptions when copying them, they will typically use a `char *` that is initialised appropriately (e.g. that points at a statically defined array, or a non-static member of a (implementation-specific) fixed length). You might consider those as options. Generally speaking, however, using an exception to carry extra data around is a bad idea (both philosophically and practically). Rather than trying to bundle extra data (strings) into an exception, find another way to communicate such information. – Peter Dec 22 '20 at 10:14
  • Thanks @Peter. Can you point me to any good resources about why bundling like this is bad, and what is the right way to pass that type of information? – parktomatomi Dec 22 '20 at 10:18

1 Answers1

1

You can move the strings instead of copy them, especially since you are passing them into your constructor by value to begin with, so they have already been copied by the caller before your constructor is even entered. The std::string move constructor is marked noexcept.

MyError(std::size_t line, std::size_t column, std::string expected, std::string found) 
    : std::runtime_error(FormatPrettyMessage(...)), line(line), column(column), expected(std::move(expected)), found(std::move(found)) {}
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • Are noncopyable extensions of `std::runtime_error` allowed, given all the rules around its copy constructor? – parktomatomi Dec 22 '20 at 09:54
  • @parktomatomi I don’t know what rules you are referring to, but adding `std::string` members to any class, `std::runtime_error` or otherwise, are not noncopyable. – Remy Lebeau Dec 22 '20 at 09:58
  • Sorry for the confusion. I was operating under the assumption that my exception must have a noexcept copy constructor, and was asking how to meet that requirement with a string member. I edited the question to try to make more sense. – parktomatomi Dec 22 '20 at 10:21