Since I'm on a desktop, I just pretend I have an infinite amount of memory, and running out of memory is fatal to my application no matter what.
So when your app fatally fails, wouldn't you prefer it to terminate cleanly? Let destructors run, file buffers or logs to be flushed, maybe even display an error message (or even better, a bug reporting screen) to the user?
With that in mind, why shouldn't I embed std::string objects in my exception classes? In fact, why couldn't my exception classes be full-featured, and also take care of logging, stack tracing, etc. I'm aware of the one-responsibility principle, and it seems to me to be a fair trade-off to have the exception class do all that.
Why is that a fair trade-off? Why is it a trade-off at all? A trade-off implies that you make some concessions to the Single-Responsibility Principle , but as far as I can see, you don't do that. You simply say "my exception should do everything". That's hardly a trade-off.
As always with the SRP, the answer should be obvious: What do you gain by making the exception class do everything? Why can't the logger be a separate class? Why does it have to be performed by the exception? Shouldn't it be handled by the exception handler? You may also want to localization, and provide syntax error messages in different languages. So your exception class should, while being constructed, go out and read external resource files, looking for the correct localized strings? Which of course means another potential source of errors (if the string can't be found), adds more complexity to the exception, and requires the exception to know otherwise irrelevant information (which language and locale settings the user uses). And the formatted error message may depend on how it is being shown. Perhaps it should be formatted differently when logged, when shown in a message box, or when printed to stdout. More problems for the exception class to deal with. And more things that can go wrong, more code where errors can occur.
The more your exception tries to do, the more things can go wrong. If it tries to log, then what happens if you run out of disk space? Perhaps you also assume infinite disk space, and just ignore that if and when it happens, you'll be throwing away all the error information?
What if you don't have write permission to the log file?
From experience, I have to say that there are few things more annoying than not getting any information about the error that just occurred, because an error occurred. If your error handling can't handle that errors occur, it isn't really error handling. If you exception class can't handle being created and thrown without causing more exceptions, what is the point?
Normally, the reason for the SRP is that the more complexity you add to a class, the harder it is to ensure correctness, and to understand the code. That still applies to exception classes, but you also get a second concern: The more complexity you add to the exception class, the more opportunities there are for errors to occur. And generally, you don't want errors to occur while throwing an exception. You're already in the middle of handling another error, after all.
However, the rule that "an exception class should not contain a std::string
isn't quite the same as "an exception class is not allowed to allocate memory". std::exception
does the latter. It stores a C-style string after all. Boost just says not to store objects which may throw exceptions. So if you allocate memory, you just have to be able to handle the case where allocation fails.
Surely, if my parser needs to report a syntax error, an full-featured exception would be more helpful than an exception built around a statically allocated character array.
Says the person who just said he didn't mind the app just terminating with no feedback to the user if an error occurred. ;)
Yes, your exception should contain all the data you need to produce a friendly, readable error message. In the case of a parser, I'd say that would have to be something like:
- Input file name (or a handle or pointer which allows us to fetch the filename when needed)
- The line number at which the error occurred
- Perhaps the position on the line
- The type of syntax error that occurred.
Based on this information, you can when handling the error produce a nice friendly, robust error message for the user. You can even localize it if you like. You can localize it when you handle the exception.
Generally, exception classes are for use by the programmer. They should not contain or construct text aimed at the user. That can be complex to create correctly, and should be done when handling the error.