Topic: Exceptions specifications


Author: herbs@cntc.com (Herb Sutter)
Date: 1998/07/25
Raw View
Valentin Bonnard <bonnardv@pratique.fr> wrote:
>David Abrahams <abrahams@motu.com> writes:
>> struct OpenFile
>> {
>>   OpenFile( const FileSpec& );
>>   ~OpenFile() {
>>     if (!CloseSucceeded() && !std::uncaught_exception())
>>       throw CloseFailure();
>>   }
>> };
>
>exceptions should never be forgoten implicitly

Right (because then the code isn't exception-neutral), but more to the
point: Exceptions should NEVER be allowed to propagate from destructors.
If OpenFile::~OpenFile can throw, then:

- You can't use OpenFile to instantiate a standard container (forget about
writing "list<OpenFile>" or "vector<OpenFile>").

- You can't reliably new[] or delete[] OpenFiles (forget about writing
"new OpenFile[10]").

[I covered these points in more detail in the Nov-Dec 1997 C++ Report. An
updated version of that article will also be available soon on Scott
Meyers' Effective C++ / More Effective C++ CD-ROM.]

Member functions usually throw so that calling code can recover and
perhaps retry the failed operation. Here there is no reason to throw
CloseFailure() because there is conceptually no recovery from a failed
dtor anyway -- even if something meaningful were done in a handler,
there's no way to run the dtor again.

In summary, I've tentatively come to the conclusion that calling
std::uncaught_exception() is probably always a bad idea and a sign of
incorrect code. In this case, OpenFile's behaviour (and therefore its
published interface contract) is inconsistent; it should not change based
on whether an exception is active.

Herb


---
Herb Sutter (mailto:herbs@cntc.com)

Current Network Technologies Corp  2695 North Sheridan Way, Suite 150
www.cntc.com www.peerdirect.com    Mississauga Ontario Canada L5K 2N6
---
[ 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:46:09 GMT, herbs@cntc.com (Herb Sutter) wrote:

>Valentin Bonnard <bonnardv@pratique.fr> wrote:
>>David Abrahams <abrahams@motu.com> writes:
>>> struct OpenFile
>>> {
>>>   OpenFile( const FileSpec& );
>>>   ~OpenFile() {
>>>     if (!CloseSucceeded() && !std::uncaught_exception())
>>>       throw CloseFailure();
>>>   }
>>> };
>>
>>exceptions should never be forgoten implicitly

I wasn't going to rise to that bait, because I thought that a terse
example in this case would do more to illustrate a useful technique
than the much longer and (potentially) complete elaboration of the
technique that Valentin posted. I thought that would be understood,
but it appears not...

Whether or not exceptions should sometimes be forgotten is really up
to the needs of a particular application. I'm not sure about the
"implicitly" part. In fact, the only way to "forget" an exception is
with a catch() clause that doesn't re-throw. Since I'm not doing that
in my example, I think all you can say is that my code can under some
circumstances allow a run-time error to occur without reporting it.
That may be appropriate for some applications under some
circumstances.

>Right (because then the code isn't exception-neutral),

Most code should in fact be exception-neutral, but slavish devotion to
that ideal is counterproductive. If all code were exception-neutral,
every exception would lead to termination, because no code would be
allowed to catch an exception without rethrowing it ;)

There exist situations where:
a. errors are best left unreported
b. exceptions should not be propagated

These are separate distinctions. Neither occurs often, but both come
up in real-life.

>but more to the
>point: Exceptions should NEVER be allowed to propagate from destructors.
>If OpenFile::~OpenFile can throw, then:
>
>- You can't use OpenFile to instantiate a standard container (forget about
>writing "list<OpenFile>" or "vector<OpenFile>").
>
>- You can't reliably new[] or delete[] OpenFiles (forget about writing
>"new OpenFile[10]").

I clearly should be more careful to spell out details in any example I
post. Add a private (never defined) copy constructor and operator
new[] to OpenFile, and these objections are moot.

>[I covered these points in more detail in the Nov-Dec 1997 C++ Report. An
>updated version of that article will also be available soon on Scott
>Meyers' Effective C++ / More Effective C++ CD-ROM.]
>
>Member functions usually throw so that calling code can recover and
>perhaps retry the failed operation. Here there is no reason to throw
>CloseFailure() because there is conceptually no recovery from a failed
>dtor anyway -- even if something meaningful were done in a handler,
>there's no way to run the dtor again.

If the object manages a resource, as this one does, there might be
some recovery from a failure to release that resource. For example, it
might be important to report that failure.

>In summary, I've tentatively come to the conclusion that calling
>std::uncaught_exception() is probably always a bad idea and a sign of
>incorrect code. In this case, OpenFile's behaviour (and therefore its
>published interface contract) is inconsistent; it should not change based
>on whether an exception is active.

No class is complete without the documentation which describes how it
may (and may not) be used, and what its behavior is. The OpenFile
class was designed to be used only in specific ways. A really complete
implementation would also include documentation describing its
behavior, and forbidding it to be used as a member or base class.

Here's another useful way to take advantage of
std::uncaught_exception(). Its use is legitimate and not a sign of
anything incorrect.

// Use this class to overwrite an existing file
// atomically. As with OpenFile, many details missing.
// No nitpicking, please!
class SafeFileOverwrite
{
  SafeFileOverwrite( FileSpec origFile )
    : originalFile( origFile ), tempfile( OpenTemporaryFile() ) {}

  ~SafeFileOverwrite()
  {
    CloseTempFile();
    if ( !std::uncaught_exception() )
      SwapFiles( tempFile, originalFile ); // OS guarantees atomicity
    DeleteTempFile();
  }
private:
  void CloseTempFile() throw();
  void DeleteTempFile() throw();

  FileSpec originalFile, tempFile;
}

The basic pattern for my use of std::uncaught_exception() is always
the same. I use it to eliminate the need for a programmer to remember
to call some function to register succcess at the close of a scope.
Any time I can write a class which leaves less room for erroneous use,
I'll take the opportunity.

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