I have to address this because it is so very relevant to your framing of this issue, but before I say anything let me quote something else relevant to this quote.
It is impossible to catch all user errors and I would argue that one should not try b/c no matter how many user errors or babysitters you code into your system you end up getting smarter idiots that still crash the system.
And with that one more.
If you try to pop an empty stack it should do absolutely nothing since the stack is empty.
Okay. Now with those quotes in mind lets take a minute to discuss why you are arguing mutually exclusive ideals.
Is it really a critical error that some moron on the outside requested a pop b/c he/she cannot properly use the stack?
If you are not going to "babysit" the client code you can't wrap every routine with precondition guards that would allow for the operation to "do nothing" in the face of popping from an empty stack. "Babysitting", "Precondition Guards", "Exception Mechanisms" or "Simple Design" it doesn't really matter what you call it; the nature of the operation can't work with an empty stack so you either check for an empty stack or very likely crash the application. Expecting the application to crash in the face of violating a condition is fine, but that's not what you are advocating.
If you are going to "babysit" the client code you can safely catch the inconstancy of "remove something that doesn't exit" you would, for the sake of consistency, be expected to code similar checks into every function. By definition you are advocating "babysitting" because you are suggesting that such operations, which for example includes "access array element that doesn't exist", should not yield errors or unexpected behavior despite of their obvious error nature.
Sure the client code could be littered with tons of pop statements when they are doing nothing but I do not think it is the responsibility of the stack class to ensure that client code doesn't call pop 50 times when the stack is empty.
But that responsibility is exactly what you've placed on the library code. If you think the way the error is caught matters, I'm afraid to say that you are wrong. The method of handling the error is irrelevant. It doesn't matter if you check for an empty stack and "do nothing", raise an exception, or return a boolean; all of those options, every one, is putting the responsibility of protecting against misuse on the library code.
This is true regardless of the method used to handle the error. If my implementation throws an exception in the considered case the user would have the option to check the preconditions themselves before attempting to call the method.
If you put an empty() function in the stack interface then there is enough information in the interface for clients of the stack class to use it properly.
Yes. As could this:
As well this could be documented in the design docs for the stack which would explain the behavior.
The `void Stack<???>::pop()' method removes the "topmost" element, the last element placed onto the stack with the most recent call to the `void Stack<???>::push(ElemT &)' method.
The `bool Stack<???>::isEmpty()' method must return `false'.
In debug mode, `NDEBUG' is not defined, the `void Stack<???>::pop()' method will raise an exception upon precondition violation, otherwise violation of the precondition is undefined.
If you throw you then force the client to wrap every pop in a try / catch which is far uglier than a simple if.
Well, I wasn't going to respond to this, but let me go ahead and smack that notion out of you.
Such a notion is simply false; I don't know where you got the notion, but you need to relieve yourself of that ignorance as soon as is possible.
You never need to use `try' and `catch' blocks unless you are going to do something with the exception.
Looking at the considered case, let us compare the three options.
If client code knows the precondition is valid, no option requires special handling.
Assuming a "do nothing" approach if client code doesn't know if the operation is valid and successful continuation requires a successful `pop' the client code must literally guess.
Assuming a "return a boolean value" approach if client code doesn't know if the operation is valid and successful continuation requires a successful `pop' the client code must check every attempt.
Assuming a "raise an exception" approach if client code doesn't know if the operation is valid and successful continuation requires a successful `pop' the client may assume that the operation was successful for if not the exception mechanism would have triggered and processing would not have continued.