Reputation: 375
How to achieve exception-free C++ assignment-expression in throwing statement without sacrificing code readability and maintainability?
Recently I'm trying to figure out a way to avoid throwing exception in the assignment-expression part of C++ throw statement. But I cannot find a way to accomplish my goal without sacrificing readability and ease of maintainability.
To be more precise, I'm trying to conform to MISRA-C++ 2008 Rule 15-1-1 (page 157).
Before the issue came to my notice from the static program analysis tool used in the company, my throwing statement would often looks something like this
int someValue;
if (/*something is wrong*/) {
throw MyExceptionClass(string("Blah some message [").
append(to_string(somevalue)).
append("]"));
}
Basically I would dump the relevant context out into a standard string, pass the said string into constructor of the soon-to-be-thrown exception then throws it.
The problem is either my custom to-string function (e.g., wrapper for inet_ntop
) or the std::to_string
function may throw exceptions (in this case, std::bad_alloc
). What happens next is the exception thrown in the assignment-expression would be propagated, instead of the original-to-be-thrown exception. Since the original exception was never constructed successfully, effectively shadowing the original exception.
In order to abide by the rule, I think I would have to completely steer away from dynamic allocation. That leaves me with
if (/*something is wrong*/) {
throw MyExceptionClass("Something is wrong");
}
int someValue;
if (/*something is wrong*/) {
char buf[ERROR_MESSAGE_SIZE] = {0};
size_t bufIndex = 0;
int ret = snprintf(buf, sizeof(buf), "Something is wrong, somevalue: [%d]", someValue);
if (ret < 0) {
/* Deal with the sprintf error here, at this point probably would just abort */
/* Note that throwing stuff here is also effectively equivalent of shadowing the original exception */
} else if (ret >= sizeof(buf)) {
/* The message is written, but truncated, not sure how to handle that (if meaningful context is dropped) */
}
throw MyExceptionClass(buf);
}
The problem with solution 1 is there is not sufficient context given in the message. If someone is calling my function / library but insisting in sending illegal argument, and at the same time ignore returned error code / no proper handling of any exception (more often than not these things will happen in the same piece of garbage code). It would take some extra time in figuring out what the current state inside my own code.
As a side note here, I wrap all my throwing statement with a layer of extra macro I wrote (omitted in the given example) using the compiler provided __FILE__
, __LINE__
and __func__
macro.
So in this case I'm not actually worried about figuring out where the actual exception is thrown.
Basically means in all my exception-handling part of the code I would have to use pure C language. The code would bloat really quickly, and not inline with the coding style of the rest of the code, making it hard to track and maintain. Plus after a while someone would probably try to come up with a common helper function and fuck up again.
std::string_view
std::to_char
Originally I thought these two relatively new feature in C++ could help me a bit (haven't actually use them before to be honest).
But after some thought and looking at the document, these two still requires a pre-allocated buffer to work on (an std::string
on the frist one, and a char
buffer on the second.
Plus I still have to check for error code return in the to_char
case.
I wish know if there are other options that can abide by the rule without significantly sacrifice the readability and maintainability. Or maybe I should stand proud and strong behind my current implementation arguing it's currently at a acceptable balance between maintainability and robustness, then happily click the ignore button on the static code analysis tool?
Also, come to think of it, how does std::throw_with_nested
deal with the problem?
I would imagine having to construct a new exception would take up some more memory space, does that mean std::throw_with_nested
may throw some other stuff in the process? (The cpp reference API does not have noexcept
on the function itself)
Thanks for any suggestion / feedback in advance, cheers.
Upvotes: 3
Views: 339
Reputation: 157464
int someValue;
if (/*something is wrong*/) {
auto ex = MyExceptionClass("Blah some message");
try {
ex = MyExceptionClass(string("Blah some message [").
append(to_string(somevalue)).
append("]"));
}
catch (...)
{
}
throw ex;
}
Generally, you want your MyExceptionClass
to contain a char const*
to a static description string (literal), and an auxiliary data element (of type e.g. std::string
) that supplements or replaces the static description string. You swallow exceptions populating the auxiliary data, so that if this process fails you still have the static description string to fall back on.
Upvotes: 1