Topic: Exceptions specifications (Was: C++ as PL/1)


Author: Christopher Eltschka <celtschk@physik.tu-muenchen.de>
Date: 1998/08/05
Raw View
David Abrahams wrote:
>
> On 25 Jul 98 07:48:07 GMT, Marcelo Cantos
> <marcelo@janus.mds.rmit.edu.au> wrote:
>
> >abrahams@motu.com (David Abrahams) writes:
> >
> >> On 23 Jul 1998 05:51:15 GMT, zalman@netcom.com (Zalman Stern) wrote:
> >>
> >> >Actually, a possible solution is to have an explicit Close method that can
> >> >be called to get the exception to not happen in the destructor. If Close
> >> >has already been called, the destructor is guaranteed to not throw an
> >> >exception.
> >...
> >> OpenFile f;
> >> try {
> >>    DoStep2();
> >> }
> >> catch(...) {
> >>   try {
> >>     f.Close();
> >>   }
> >>   catch(...) {} // <= Really inconvenient catch clause!
> >>   throw;
> >> }
> >> f.Close();
> >>
> >> Furthermore, you have to remember to explicitly close the file. I
> >> don't see the advantage.
> >
> >Implicit in Zalman's suggestion was that the destructor closes the
> >file but doesn't throw an exception on failure.  This results in:
> >
> >   OpenFile f;
> >   DoStep2();
> >   f.Close();
> >
> >With identical semantics to David's example, making Zalman's
> >suggestion quite a good one.
>
> With this idiom, if the programmer forgets to write f.Close() and
> closing the file fails all by itself (not during another exception),
> then it fails silently. Throwing the exception from the destructor
> (but only when std::uncaught_exception() returns false) doesn't
> require a programmer to remember to call Close() in order to have the
> failure reported. Some might consider that an advantage.

The problem with std::uncaught_exception() is that it doesn't really
tell you if throwing is allowed. Assume f.ex. the following:

class X
{
public:
  ~X();
// ...
};

X::~X()
{
  try
  {
    OpenFile f("log");
    f.Write("X destructed");
  }
  catch(...)
  {
    cerr << "Warning: Logging failed" << endl;
  }
}

void f()
{
  X x;
  throw some_exception;
}

int main()
{
  try
  {
    f();
  }
  catch(...)
  {
    return 1;
  }
}

Now, when f throws, x gets destructed as part of stack unwinding.
The destructor of x tries to log that. Say, however, the destructor
(which we assume should also write still cached data to the file)
fails. Normally this would lead to an exception which is catched
inside the constructor, to warn about that failure. But in this
specific case, std::uncaught_exception() tells the destructor
(correctly) that there's an uncaught exception lying around
(namely the one thrown by f), and therefore OpenFile::~OpenFile()
won't throw, although it is safe to throw. Therefore the logging
failure will remain unnoticed, although it would have been
completely safe to throw.
Of course, in this case this just means an error message is not
displayed. However, who knows what it means in other cases?

OTOH, if you look at Zalman's post again, you'll find there:
*If Close has already been called*, the destructor is guaranteed
to not throw an exception. (Emphasis added by me). That is,
if you don't call Close yourself, the destructor *may* throw
an exception (and therefore, there's no unnoticed failure if
the destructor does so). If you have called Close yourself,
however, you *ensure* that there's no throw, since you did
everything which *could* fail yourself (that is, the destructor
won't throw because nothing will fail).

So Zalman's solution gives you the benefit of being able to control
*where* the error occurs, without having the danger of an unnoticed
failure (the only danger is that forgetting to call Close might
cause two exceptions crashing).
Your solution OTOH gives you no control about *where* the exception
occurs, but suppresses the exception if another one is active. This
gives you the advantage that you generally get noticed of failure,
and automatically avoid crashing exceptions. However, the disadvantage
is that in some cases, the failure will remain unnoticed. And since
the cause of this may be buried deep in the call stack, it's not
quite easy to spot (if you at all find out that a problem got
unnoticed).


[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: Marcelo Cantos <marcelo@janus.mds.rmit.edu.au>
Date: 1998/07/25
Raw View
abrahams@motu.com (David Abrahams) writes:

> On 23 Jul 1998 05:51:15 GMT, zalman@netcom.com (Zalman Stern) wrote:
>
> >Actually, a possible solution is to have an explicit Close method that can
> >be called to get the exception to not happen in the destructor. If Close
> >has already been called, the destructor is guaranteed to not throw an
> >exception.
...
> OpenFile f;
> try {
>    DoStep2();
> }
> catch(...) {
>   try {
>     f.Close();
>   }
>   catch(...) {} // <= Really inconvenient catch clause!
>   throw;
> }
> f.Close();
>
> Furthermore, you have to remember to explicitly close the file. I
> don't see the advantage.

Implicit in Zalman's suggestion was that the destructor closes the
file but doesn't throw an exception on failure.  This results in:

   OpenFile f;
   DoStep2();
   f.Close();

With identical semantics to David's example, making Zalman's
suggestion quite a good one.


Cheers,
Marcelo

--
______________________________________________________________________
Marcelo Cantos, Research Assistant       __/_  marcelo@mds.rmit.edu.au
Multimedia Database Systems Group, RMIT   /       _ Tel 61-3-9282-2497
L2/723 Swanston St, Carlton VIC 3053, Aus/ralia ><_>Fax 61-3-9282-2490
Acknowledgements: errors - me; wisdom - God; sponsorship - RMIT
---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]





Author: abrahams@motu.com (David Abrahams)
Date: 1998/07/27
Raw View
On 25 Jul 98 07:48:07 GMT, Marcelo Cantos
<marcelo@janus.mds.rmit.edu.au> wrote:

>abrahams@motu.com (David Abrahams) writes:
>
>> On 23 Jul 1998 05:51:15 GMT, zalman@netcom.com (Zalman Stern) wrote:
>>
>> >Actually, a possible solution is to have an explicit Close method that can
>> >be called to get the exception to not happen in the destructor. If Close
>> >has already been called, the destructor is guaranteed to not throw an
>> >exception.
>...
>> OpenFile f;
>> try {
>>    DoStep2();
>> }
>> catch(...) {
>>   try {
>>     f.Close();
>>   }
>>   catch(...) {} // <= Really inconvenient catch clause!
>>   throw;
>> }
>> f.Close();
>>
>> Furthermore, you have to remember to explicitly close the file. I
>> don't see the advantage.
>
>Implicit in Zalman's suggestion was that the destructor closes the
>file but doesn't throw an exception on failure.  This results in:
>
>   OpenFile f;
>   DoStep2();
>   f.Close();
>
>With identical semantics to David's example, making Zalman's
>suggestion quite a good one.

With this idiom, if the programmer forgets to write f.Close() and
closing the file fails all by itself (not during another exception),
then it fails silently. Throwing the exception from the destructor
(but only when std::uncaught_exception() returns false) doesn't
require a programmer to remember to call Close() in order to have the
failure reported. Some might consider that an advantage.
---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]





Author: abrahams@motu.com (David Abrahams)
Date: 1998/07/22
Raw View
On 21 Jul 1998 21:38:58 GMT, abrahams@motu.com (David Abrahams) wrote:

>On 21 Jul 1998 18:12:19 GMT, Valentin Bonnard <bonnardv@pratique.fr>
>wrote:
>
>>David Abrahams wrote:
>>
>>> In other words, if T::~T() has no throw()
>>> specification, and is not inline, the compiler still has to generate
>>> the try/catch block for auto_ptr<T>::~auto_ptr()... yes?
>>
>>Did you forgot my question in Morristown ?
>>
>>I asked why auto_ptr<T>::~auto_ptr() did NOT had an exception
>>spec. The answer was that it's undefined behaviour to throw
>>an exception there anyway.
>
>Valentin,
>
>I suppose I did forget your question, although I don't see how it
>matters. From FDIS:
>
>~auto_ptr() throw();
>
>So it *does* have an exception specification.
>...
>
>Oh, maybe you were asking why a particular auto_ptr IMPLEMENTATION
>didn't have an exception specification. It is true that the standard
>forbids T::~T() to throw an exception if std::auto_ptr<T> is ever
>used. That's fortunate! Since there is no way for the user to detect
>it, an implementation is free to omit the throw() specification on
>auto_ptr. I hope that implementors will take advantage of this fact.

Unfortunately, I think I was wrong. The standard says [15.4/13]:

-13- An implicitly declared special member function (clause special)
shall have an exception-specification. If f is an implicitly declared
default constructor, copy constructor, destructor, or copy assignment
operator, its implicit exception-specification specifies the type-id T
if and only if T is allowed by the exception-specification of a
function directly invoked by f's implicitly definition; f shall allow
all exceptions if any function it directly invokes allows all
exceptions, and f shall allow no exceptions if every function it
directly invokes allows no exceptions. [Example:

struct A {
    A();
    A(const A&) throw();
    ~A() throw(X);
};
struct B {
    B() throw();
    B(const B&) throw();
    ~B() throw(Y);
};
struct D : public A, public B {
 //  Implicit declaration of  D::D();
 //  Implicit declaration of  D::D(const   D&)   throw();
 //  Implicit declaration of  D::~D()   throw   (X,Y);
};
Furthermore, if A::~A() or B::~B() were virtual, D::~D() would not be
as restrictive as that of A::~A, and the program would be ill-formed
since a function that overrides a virtual function from a base class
shall have an exception-specification at least as restrictive as that
in the base class. ]

-----
So if we have no throw() specification on ~auto_ptr(),

struct A {
    virtual ~A() throw(X);
};

struct D : A, auto_ptr<int>
{
};

The implicit declaration of  D::~D() has no throw() specification, and
thus is ill-formed. With the regular definition of ~auto_ptr()
throw(), we have D::~D() throw(X), which is well-formed. This appears
to be the only way in which throw() specifications are detectable at
compile-time.

-Dave


[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: zalman@netcom.com (Zalman Stern)
Date: 1998/07/23
Raw View
David Abrahams (abrahams@motu.com) wrote:
: struct OpenFile
: {
:   OpenFile( const FileSpec& );
:   ~OpenFile() {
:     if (!CloseSucceeded() && !std::uncaught_exception())
:       throw CloseFailure();
:   }
: };

I strongly agree that closing a file can fail, but I disagree that in such
situations this should be rolled into a destructor. In such a situation, an
explicit Close method is a better bet. E.g. Due to an exception from
another method, I already know that the file is toast and I just want to
get rid of it. How are you going to provide a "nothrow" destructor? Or are
you going to make everyone code a really incovenient catch clause?
Actually, a possible solution is to have an explicit Close method that can
be called to get the exception to not happen in the destructor. If Close
has already been called, the destructor is guaranteed to not throw an
exception. However there is very little chance of static analysis proving
that an exception will not be thrown by the destructor and hence the
optimization being discussed can't occur.

All in all, I'm not willing to call this example "useful."

-Z-


[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: abrahams@motu.com (David Abrahams)
Date: 1998/07/23
Raw View
On 23 Jul 1998 05:51:15 GMT, zalman@netcom.com (Zalman Stern) wrote:

>David Abrahams (abrahams@motu.com) wrote:
>: struct OpenFile
>: {
>:   OpenFile( const FileSpec& );
>:   ~OpenFile() {
>:     if (!CloseSucceeded() && !std::uncaught_exception())
>:       throw CloseFailure();
>:   }
>: };
>
>I strongly agree that closing a file can fail, but I disagree that in such
>situations this should be rolled into a destructor. In such a situation, an
>explicit Close method is a better bet. E.g. Due to an exception from
>another method, I already know that the file is toast and I just want to
>get rid of it. How are you going to provide a "nothrow" destructor? Or are
>you going to make everyone code a really incovenient catch clause?

In this case, the exception is only thrown if we're not already
handling an exception. Otherwise, an error closing the file is ignored
(sometimes that's the best you can do). No "really inconvenient catch
clause" is needed.

>Actually, a possible solution is to have an explicit Close method that can
>be called to get the exception to not happen in the destructor. If Close
>has already been called, the destructor is guaranteed to not throw an
>exception.

That begs the question. Suppose you want to do this:

1.Open a file
2. Do something possibly unrelated to the file which might throw
3. Close the file

I'm postulating that the file needs to be closed whether or not step 2
throws an exception. Then you're left with this:

OpenFile f;
try {
   DoStep2();
}
catch(...) {
  try {
    f.Close();
  }
  catch(...) {} // <= Really inconvenient catch clause!
  throw;
}
f.Close();

Furthermore, you have to remember to explicitly close the file. I
don't see the advantage.

>However there is very little chance of static analysis proving
>that an exception will not be thrown by the destructor and hence the
>optimization being discussed can't occur.
>
>All in all, I'm not willing to call this example "useful."

The usefulness of the example has nothing whatever to do with
optimization.



[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: Valentin Bonnard <bonnardv@pratique.fr>
Date: 1998/07/24
Raw View
David Abrahams wrote:

> The implicit declaration of  D::~D() has no throw() specification, and
> thus is ill-formed. With the regular definition of ~auto_ptr()
> throw(), we have D::~D() throw(X), which is well-formed. This appears
> to be the only way in which throw() specifications are detectable at
> compile-time.

On a normal (non special) function:

T (*g) (U1, U2) throw () = &f;
T (U1::*h) (U2) throw () = &U1::f;

the correctness depends on the declarations of ::f and U1::f.

But, if ::f and U1::f are standard functions, at least I don't
think that this kind of code is standard conforming, as the
functions could have default arguments.

--

Valentin Bonnard                mailto:bonnardv@pratique.fr
info about C++/a propos du C++: http://pages.pratique.fr/~bonnardv/


[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: Valentin Bonnard <bonnardv@pratique.fr>
Date: 1998/07/24
Raw View
Message to everyone: some month ago (or was it some years
ago ?), I took part in a very long thread about exceptions
escaping from destructors in comp.lang.c++.moderated.

There were interresting arguments as well as repetitive
unjustified claims. I think that every point was well
explainned. Please go to DejaNews before restarting the
whole thing. If you have some new ideas, you can post it.

Of course you are free to post whatever you want; I am
not a moderator.

David Abrahams <abrahams@motu.com> writes:

> Yes:

I agree

> struct OpenFile
> {
>   OpenFile( const FileSpec& );
>   ~OpenFile() {
>     if (!CloseSucceeded() && !std::uncaught_exception())
>       throw CloseFailure();
>   }
> };

exceptions should never be forgoten implicitly (except in
exceptional cases - sorry). I would write that as:

struct OpenFile
{
  void (*emergercy_error_handler)(OpenFile*); // throw ()

  OpenFile( const FileSpec& );

  ~OpenFile() {
    if (!CloseSucceeded())
      if (std::uncaught_exception())
        emergercy_error_handler (this);
      else
        throw CloseFailure();
  }

private:
  OpenFile (OpenFile&); // of course !
  void operator= (OpenFile&);
};

emergercy_error_handler should better not throw, but it
can:
- log the error on cerr, or whatever logs
- put an exception object in a list, for later treatment
  (my prefered solution)
- abort

--

Valentin Bonnard                mailto:bonnardv@pratique.fr
info about C++/a propos du C++: http://pages.pratique.fr/~bonnardv/


[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: AllanW@my-dejanews.com
Date: 1998/07/22
Raw View
In article <35B4D902.2781E494@pratique.fr>,
  Valentin Bonnard <bonnardv@pratique.fr> wrote:
> David Abrahams wrote:
>
> > In other words, if T::~T() has no throw()
> > specification, and is not inline, the compiler still has to generate
> > the try/catch block for auto_ptr<T>::~auto_ptr()... yes?
>
> Did you forgot my question in Morristown ?
>
> I asked why auto_ptr<T>::~auto_ptr() did NOT had an exception
> spec. The answer was that it's undefined behaviour to throw
> an exception there anyway.



Author: abrahams@motu.com (David Abrahams)
Date: 1998/07/22
Raw View
On 22 Jul 98 19:06:34 GMT, AllanW@my-dejanews.com wrote:

>That's the negative side of throwing from a destructor.  Is there
>any positive side?  That is, does throwing from a destructor ever
>serve any useful purpose?

Yes:

struct OpenFile
{
  OpenFile( const FileSpec& );
  ~OpenFile() {
    if (!CloseSucceeded() && !std::uncaught_exception())
      throw CloseFailure();
  }
};


[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: Valentin Bonnard <bonnardv@pratique.fr>
Date: 1998/07/21
Raw View
David Abrahams wrote:

> In other words, if T::~T() has no throw()
> specification, and is not inline, the compiler still has to generate
> the try/catch block for auto_ptr<T>::~auto_ptr()... yes?

Did you forgot my question in Morristown ?

I asked why auto_ptr<T>::~auto_ptr() did NOT had an exception
spec. The answer was that it's undefined behaviour to throw
an exception there anyway.

--

Valentin Bonnard                mailto:bonnardv@pratique.fr
info about C++/a propos du C++: http://pages.pratique.fr/~bonnardv/


[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: abrahams@motu.com (David Abrahams)
Date: 1998/07/21
Raw View
On 21 Jul 1998 18:12:19 GMT, Valentin Bonnard <bonnardv@pratique.fr>
wrote:

>David Abrahams wrote:
>
>> In other words, if T::~T() has no throw()
>> specification, and is not inline, the compiler still has to generate
>> the try/catch block for auto_ptr<T>::~auto_ptr()... yes?
>
>Did you forgot my question in Morristown ?
>
>I asked why auto_ptr<T>::~auto_ptr() did NOT had an exception
>spec. The answer was that it's undefined behaviour to throw
>an exception there anyway.

Valentin,

I suppose I did forget your question, although I don't see how it
matters. From FDIS:

~auto_ptr() throw();

So it *does* have an exception specification.
...

Oh, maybe you were asking why a particular auto_ptr IMPLEMENTATION
didn't have an exception specification. It is true that the standard
forbids T::~T() to throw an exception if std::auto_ptr<T> is ever
used. That's fortunate! Since there is no way for the user to detect
it, an implementation is free to omit the throw() specification on
auto_ptr. I hope that implementors will take advantage of this fact.

-Dave


[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]