I'm implementing a queue, and I was wondering, what should I do when a user misuses the container?
For example I have two methods, Front and Pop, which never throw (I static_assert that the destructor for the contained element is noexcept) so long as they're not called on an empty queue. I could add a check to these that throws if they're called on empty queues, but then I couldn't define them noexcept.
I was thinking that it makes sense to declare these noexcept, and then say that the behavior is undefined when called on an empty queue (I provide Size and Empty methods for users to check). I could then add the check only on debug builds, so it calls terminate in debug when misused and attempts to destruct or dereference missing elements on release. I was wondering what the better approach would be.
After considering the accepted answer, I decided to follow the standard. Vector's pop_back is not marked noexcept and has the same semantics as my Pop, so I won't mark it noexcept either. And in general, will try to avoid setting narrow contracts as noexcept.
This is not a simple question. In the general case, in C++ you would document your contract stating that the behavior is undefined unless the container is non-empty. The implication is that a call out of context can cause any possible behavior. The use of noexcept
in such interfaces then limits what the range of any possible behavior is: it is any possible behavior that does not involve throwing an exception across the boundary.
Why is this important? Where I work we use BSL which is roughly an implementation of an extended C++03 standard library. Part of the library includes utilities for defensive programming and testing. Rather than using assert
, the library uses its own flavor of assertion BSLS_ASSERT
macros to verify contract violations. The library is built such that user code can control what happens when an assertion is triggered, and that is effectively used in test drivers to verify not only positive behavior (the component does what it should) and negative behavior (it does not do what it shouldn't) but also that it has the appropriate checks for contract violations. For that, an assert handler that throws a particular form of exception is used, and the test driver can then call out of contract and verify that the component is checking the behavior…
Long winded story, the point is that if that if the functions with narrow contracts (can be called out of contract) are marked as noexcept
because the implementation should never throw (calling front()
on an container), then when called out of contract it cannot throw, and the above mechanism cannot be used to verify that the component will detect contract violations.
This was part of the reason for a late revision of the C++11 standard (just before approval) to remove noexcept
from all functions with a narrow contract. You can read more in this proposal