(Warning: rant)
I think we had a discussion about exceptions before, but I don't remember it going anywhere.
http://www.cplusplus.com/forum/beginner/142576/#msg752510Cubbi wrote: |
---|
It's hard to find a guide worse than Google's (the exception ban is only a tip of the iceberg) |
LB wrote: |
---|
What's so wrong with the exception ban? |
Cubbi wrote: |
---|
It turns C++ into a fundamentally different language, one without RAII or guaranteed class invariants. |
The last time I checked, RAII didn't depend on exceptions, nor were exceptions the only case where RAII was useful. As for invariants, I don't see how removing exceptions removes the guarantee of class invariants. That's like saying that an application which never throws an exception while it runs has no class invariants while it runs. If a tree falls in the forest... it still falls.
I don't like exception handling.
The three common things I've seen happen when you throw an exception are:
- The exception is caught, logged, and ignored (e.g. passing events to plugins)
- The exception is caught and the application handles it (e.g. message box to user, 500 internal server error, etc)
- The exception goes all the way up the entire call stack and ends the application (e.g. ran out of memory)
I need not mention abuse of exceptions to control program flow.
The way you design code with and without exceptions is dramatically different. I've done both in the past. Whenever I've needed to throw an exception it was because I allowed a wider range of inputs than I could actually work with. When I write code without exceptions, I don't allow that. If a conversion needs to take place from an unsafe input to a safe input, that conversion is documented. "Values below 0 will be forced to 0", not "Values below 0 will cause IndexOutOfBoundsException to be thrown".
Let's look at an example.
1 2 3 4 5 6 7
|
template<typename T>
struct MyList
{
//...
T &get(std::size_t index){/**/}
//...
};
|
This is a poorly designed interface. What happens when you try to access an index that doesn't exist? You can't take the last element in the list - what if the list is empty? Either you say it results in undefined behavior, or you throw a
std::out_of_range exception.
Returning a pointer so that
nullptr
can be returned is not good design either - that doesn't stop people from neglecting to check if the returned pointer is null and just blindly dereferencing the pointer. Instead, I would prefer this interface:
1 2 3 4 5 6 7 8 9 10 11 12 13
|
template<typename T>
struct MyList
{
//...
T &get(std::size_t index, T &def = default_value()){/**/}
//...
private:
static T &default_value()
{
static T t;
return t;
}
};
|
Now, in the event that no value is present, you have a default value that can be returned instead and which the user can optionally provide. If the type doesn't have a default constructor then the user just needs to always provide a default value and the code will compile (
http://coliru.stacked-crooked.com/a/6d71917f14c7331d). There's no need to inform the user about accessing an out-of-range index because if they cared in the first place they would have called
size() first. Telling them about it when they don't care doesn't do anything. Since they work with a default value the state of the program isn't changed.
How will you debug bad code if there's no crash, no stack trace? There's the logs. What would have happened anyway if we threw an exception? It'd probably just get logged and the application would continue running. Same as always.
And think about it: what would the calling code do anyway if it checked the size and found out the index it wanted to reach didn't exist? Is that your concern? Why should it be when the calling code doesn't check? Whether the calling code checks or not doesn't influence whether you check or not.
But if that interface doesn't suit you, try this one:
1 2 3 4 5 6 7 8 9
|
template<typename T>
struct MyList
{
//...
void act_upon(std::size_t index,
std::function<void (T &)> if_valid,
std::function<void (std::size_t)> if_invalid = [](std::size_t){}){/**/}
//...
};
|
It's a bit more of a mouthful, but it now encourages design that is less likely to act weird when given the default value of the previous interface. And it lets the caller decide what to do with an error (again, if they cared they'd have checked the size themselves). The third parameter may as well not be there. Or that third parameter could be passed a throw statement if you disagree with me. In other words, exception handling has just become optional. Amazing.
In C++ there's plenty of places where you can do bad things and the language doesn't throw an exception or guarantee that your program will even continue running, and we've built walls to protect us from those. So why don't we build walls instead of throwing exceptions? It's a great mystery to me.
Or maybe we should add
std::null_pointer_exception and freely dereference pointers without ever checking if they're null. That gets checked for us, right? So why do we need to worry? We'll do something about it later when the bug reports start coming in. Hey, the number of times per month you'll need to fix it will go down over the years, don't worry. But those pesky
std::invalid_argument_exceptions will still crop up from time to time because you couldn't be bothered to fix bad designs that accept invalid arguments.
Besides everything mentioned above, the number one problem I have with exceptions is that they're basically trolls. They claim to protect you from doing bad things, but they are nowhere to be found at compile time and instead jump out in the last second at runtime and shout "boo!" at whoever happens to be running the program. Essentially they are a workaround for inability to detect certain kinds of problems at compile time.
Plenty of good-quality C code has been written without exception handling. I don't understand why making the language easier and safer to work with would require the introduction of a tool that lets you know you've done something wrong when it's already too late. "Oops, you gave me a value that is outside the range you're supposed to give me! No you're going to have to go back and do what you were too lazy to do before! Boo!"
I apologize for the rant, but exception handling really bothers me. I'm not beyond being convinced that I don't understand exception handling, but I am pretty confident that I do. I can also understand that exceptions can be a necessary evil in certain cases, but that doesn't make them less evil to me. So how many of my points do you disagree with? ;p