Boost logo

Boost :

From: Vesa Karvonen (vesa.karvonen_at_[hidden])
Date: 2001-08-29 12:36:46


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()).

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.

[...]
> 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.

[...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.

> > > ) 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;
      }

Also, please use reinterpret_cast<> in the implementation of construct() - it
is much easier to find.


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