Boost logo

Boost :

From: Fernando Cacciola (fcacciola_at_[hidden])
Date: 2001-08-29 13:47:32


----- Original Message -----
From: Vesa Karvonen <vesa.karvonen_at_[hidden]>
To: <boost_at_[hidden]>
Sent: Wednesday, August 29, 2001 2:36 PM
Subject: Re: [boost] Re: Proposal: statefull objects

> From: "Fernando Cacciola" <fcacciola_at_[hidden]>
> > From: Gennadiy E. Rozental <rogeeff_at_[hidden]>
> > > --- In boost_at_y..., "Fernando Cacciola" <fcacciola_at_g...> wrote:
> > > > From: Gennadiy E. Rozental <rogeeff_at_m...>
> [...]
> > > > Here are some problems:
> > > >
> > > > 1) optional<int> t = foo();
> > > > if ( t.value() == 2 ) ....
> > > >
> > > > If t is unitialized, then t.value() MUST throw, since it isn't true
> > > nor
> > > > false that its value is 2 because it has no value.
> > > I absolutely disagree. It SHUOLD NOT throw. If it will throw you will
> > > need to catch it somewhere, while again you can just check ahead (see
> > > above). And you have ho way to eliminate this - and catch exception
> > > is much more bulky way. Also do not forget that every call (even if
> > > you check already - compiler does not know about it) of value() will
> > > include implementation specific exception handling logic. So instead
> > > of transparent inline access method you propose function that will or
> > > will not be inlined (implementation dependent) and will contain
> > > exception handling logic.
> > >
> > Actually, my implementation dosen't include any exception logic in the
> > accesor function, so it will be inlined by any compiler with any
inlining
> > support.
> >
> > T const& value() const
> > {
> > if ( !v )
> > error() ;
> > return *v ;
> > }
> >
> > As it is documented in the code, the member function error() is
> > intentionally not inline, this effectively allows value() to be fully
> > inlined, and since it is only called if the expression !v is false, the
only
> > overhead is in the check.
>
> I have used a similar technique for a long time for assertion primitives,
> which throw an exception in case the invariant expression is not true.
This
> has a couple of benefits:
> A) the dependency to the exception class that is thrown is eliminated and
it
> doesn't have to be included into the translation unit at all. (I really
hate
> long compile times - they are a sign of bad engineering.)
> B) the code generated for an external function call is typically about
half
> the code generated for an exception class constructor plus the throw
> statement. (This is most useful on low end platforms, but code bloat is
never
> desirable.)
>
> I noticed that at least the benefit A is not present in the optional<>
> implementation, because the exception is included into the translation
unit.
> Benefit B is hampered by the fact that in the implemention of optional,
> error() is a template although it need not be (if you really want the
typeid,
> you can pass it to error()).
>
You are completely right.
In fact, my own version of optional<> uses something like:

T const& value() const
{
   DefectManager::instance().assert ( v );
   return *v ;
}

which presents the benefits you mentioned.

I couldn't post this since I would have to post also DefectManager, which
would obscure the proposal.

I also realized that a free function in a unique translation unit would be
more appropiate, but I wasn't sure how should I do that. Should I have
created a CPP with the error function that the user is required to add to
its project? Perhaps.

> I also noticed that you throw runtime_error instead of logic_error. I
believe
> that the meaning of logic_error is closer to the intent of optional than
> runtime_error.
>
After second though I think your are right.
After all my own argument is that the exception is thrown only becuase of a
logic error :-)

> [...]
> > You DONT NEED TO USE ANY TRY BLOCKS in order to use my optional. An
> > exception will be thrown ONLY IF YOU MAKE AN ERROR IN YOUR CODE. Don't
> > throwing such exception will just MASK the error, while throwing will
help
> > you recognize that you've made it.
> [...]
> > Once again: If you don't forget to verify that the optional is
initialized
> > you won't have any exception, but if you do forget it, you don't really
> > wont to let your program keep running because it might be based on the
false
> > belief that a particular optional has a value that it actually hasn't.
>
> Gentlemen, if my impression is correct, then mr. Rozental is mainly
concerned
> with the run-time overhead of run-time error checking, while mr. Cacciola
is
> concerned with run-time safety.
>
> In general, safety is definitely a stronger motivation. Software that does
not
> work correctly is worse than useless, because it may cause serious damage
to
> the user. Software that is just too slow to be useful generally does not
cause
> harm beyond the purchase price of the software.
>
> I'm personally quite satisfied with having the run-time check, but since
the
> exception should never been throw in correct code, i.e. it is the
> responsibility of the user to check before dereferencing optional, then
the
> run-time checking could be made to compile conditionally. A simple
technique
> would be to use the following kind of code:
>
> T const& value() const
> {
> #ifndef NDEBUG
> if ( !v )
> error() ;
> #endif
> return *v ;
> }
>
> Alternatively, the run-time check and response could be made template
> parameters of optional, making it possible to have both checked and
unchecked
> optional objects in the same application.
>
I agree that this additional testing should be optional, but turned off only
in the release version.
(mr. Rozental, would this address your concern?)

I realize that a 'failure-policy' based approach is what more choices give
to the user.

This kind of design decisions: how to turn on/off assertions and what to do
if they fail are a problem in template code because of the way templates are
instantiated. You might have an optional<int> which throws and an
optional<float> that does not, just because you instantiated them in
different contexts (assuming the traditional preprocessor based approach is
used).
An additional failure-policy template parameter might prove useful, but we
should test its engineering impact in large objects.

Perhaps at least we can try to set up an interface for such an aproach.

> [...pointer vs value discussion...]
>
> Personally, I prefer the pointer like semantics, simply because there is
no
> way to make the value semantics work perfectly.
>
> > > > Anyway, I realized a few things with my implementation seeing yours:
> > > >
> > > > ) T const& *() const ; seems to work just fine. I can't remember
> > > why I
> > > > changed it to use the proxy.
>
> Yes. I think that the proxy should be dropped.
>
Indeed.

> > > > ) I lack exceptionn saftey in operator =.
>
> You might want to use T::operator=() in the implementation:
>
> optional& operator = ( optional const& rhs )
> {
> switch (!!*this | !!rhs << 1) {
> case 0: /* NOP */ break;
> case 1: destroy(); break;
> case 2: construct(*rhs); break;
> case 3: **this = *rhs; break;
> }
> return *this;
> }
>
Hmmm. Interesting...
case 2 should call destroy() first, shouldn't?
Also, construct() might throw if T( T const&) does.
I need to think about this. Perhaps a swap with a try block would be better.

> Also, please use reinterpret_cast<> in the implementation of construct() -
it
> is much easier to find.
>
Good point.
Anyway, unfortunately this trickery is actually broken (due to alignment
constaints) :-(

I am trying to come out with some sort of aligned_storage<T> which works
with Builder 5.0.
But I'll remember to use reinterpret_cast<> in it!

Fernando Cacciola
Sierra s.r.l.
fcacciola_at_[hidden]
www.gosierra.com


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