Boost logo

Boost :

Subject: Re: [boost] Looking for thoughts on a new smart pointer: shared_ptr_nonnull
From: Thorsten Ottosen (thorsten.ottosen_at_[hidden])
Date: 2013-10-11 11:35:13


On 11-10-2013 16:29, Julian Gonggrijp wrote:
> Thorsten Ottosen wrote:
>
>> ""17.6.4.11 Requires paragraph [res.on.required]
>>
>> Violation of the preconditions specified in a function’s
>> Requires:
>> paragraph results in undefined behavior
>> unless the function’s
>> Throws:
>> paragraph specifies throwing an exception when the precondition is violated."
>
> I think you are right about this: IF a function throws when its
> precondition is violated, then the resulting behaviour is defined. I
> previously replied with +1 to Nevin when he claimed the opposite
> (among other things); I admit not studying that bit carefully enough.

No need to apologize. The literature on this subject is full of
imprecise, ambugious and non-formal definitions of the terms
precondition and postcondition. My own work is no exception. The C++
standard is no exception. Perhaps the spec # guys have some of the best
definitions in their papers (I would have to reread them to be sure). At
least they distinguish between a normal postcondition and the condition
that is guaranteed when the function exists (not returns) via an
exception. Take one definition, and all other statements, from persons
working with another definition, are close to meaningless.

So which definition should one choose? It's up to each person to decide
for themselves. I find it /practical/ and /useful/ to state that the
following two programs have a bug, and that the bug is a violation of a
precondition in both cases:

double computeSum( const vector<double>& values )
{
    double res = 0.;
    for( size_t n = 0u; n < values.size(); ++n )
      res += values[n+1];
    return res;
}

double computeSum( const vector<double>& values )
{
    double res = 0.;
    for( size_t n = 0u; n < values.size(); ++n )
      res += values.at(n+1);
    return res;
}

I don't see any benefit of saying that there is not a precondition
violation in both cases, but I see many benefits of doing so. Hence I
would prefer a formal definition that allows me to reason like that.

> This is however tangential to the question whether a function *should*
> throw when its precondition is violated. Herb Sutter wrote a piece on
> error handling [1] which I think is insightful. Herb Sutter states,
> and I think I agree, that it is the responsibility of the caller to
> ensure that preconditions are met; hence it is also the responsibility
> of the caller to report an error when it cannot meet the preconditions
> of the callee (and it cannot avoid the call). Of course you can do an
> additional check inside the callee, but that check should be
> considered to be redundant and on violation the real error is in the
> caller, not in the callee.

I don't think anybody has ever disagreed with that. That's how it is.
In a correct program, all preconditions can be removed without any
change to the program's behavior.

> Herb Sutter wrote:
>
>> The code that could cause an error is responsible for detecting and
>> reporting the error. In particular, this means that the caller is
>> responsible for detecting and reporting a to-be-called function's
>> parameter precondition violations. If the caller fails to do so, it is
>> a programming mistake; therefore, when the parameter precondition
>> violation is detected inside the called function, it can be dealt with
>> using an assertion.
>
> I recommend reading all of Sutter's piece.

It's usually worth a read.

> I think the discussion so far has concerned several points, the first
> three of which can now be closed as far as I can tell:

[snip]

> * Should a function throw when its precondition is violated?
> ~ Going with Herb Sutter: no, but the caller should throw when it is
> about to violate it.
> (In the case of shared_ptr_non_null, the caller could be a factory
> function or a wrapper with the specific purpose of checking for null.)
> * Does checking for null add overhead in the constructor of
> shared_ptr_non_null?
> ~ Not much compared to memory allocation, but it is still redundant IF
> not passing null is a precondition (if you agree with Herb Sutter).

That's one view. It's totally redundant in a correct program. In a
non-correct program we are then in the UB vs defined behavior situation.
I talked with Herb in various WG21 C++ meetings (after he published that
article I think) when we discussed contract programming. His oppinion at
that time was that it was OK to throw when a precondition is violated.
So I guess people change oppinions. I have done so over the years. Maybe
Herb changed his oppinion back.

My current oppinion is that if can avoid UB without any major overhead,
we should do so. That implies throwing when it's relatively cheap to do
so (using boost::throw_exception). It also implies that
vector<T>::front() should not be throwing. If we were to add
boost::throw_precondition that would be used for low-overhead
preconditions (defaulting to assert), then at least I could compile
our code-base with such checks.

kind regards

-Thorsten


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk