Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Throwing exceptions from constructors

64 views
Skip to first unread message

Michael Huddlestone

unread,
Jan 12, 1999, 3:00:00 AM1/12/99
to
I've heard that throwing exceptions from constructors can cause problems.
Can anyone confirm/deny/expand on this? Is the destructor called in this
case?

Thanks, Mike.


[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
[ about comp.lang.c++.moderated. First time posters: do this! ]

Valentin Bonnard

unread,
Jan 12, 1999, 3:00:00 AM1/12/99
to
Michael Huddlestone wrote:

> I've heard that throwing exceptions from constructors can cause problems.

Utter non sens.

OTOH, using exceptions is never trivial (see previous GotW).

--

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

David Abrahams

unread,
Jan 12, 1999, 3:00:00 AM1/12/99
to
On 12 Jan 1999 07:56:39 -0500, "Michael Huddlestone"
<m...@netcomuk.co.uk> wrote:

>I've heard that throwing exceptions from constructors can cause problems.

Not if you know what you're doing and your compiler works. This is the
only sane way to deal with errors that might occur during
construction. Trying to avoid throwing exceptions from constructors
can cause much bigger problems.

>Can anyone confirm/deny/expand on this? Is the destructor called in this
>case?

Not the destructor of the class being constructed, because it isn't
completely constructed yet, but the destructors of any
completely-constructed subobjects and/or base classes *are* called. If
the object was being created via operator new, the appropriate
matching operator delete is also called to release the memory.

-Dave

Francis Glassborow

unread,
Jan 12, 1999, 3:00:00 AM1/12/99
to
In article <01be3da4$b05f0260$59e92ac2@pppmdh>, Michael Huddlestone
<m...@netcomuk.co.uk> writes

>I've heard that throwing exceptions from constructors can cause
problems.
>Can anyone confirm/deny/expand on this? Is the destructor called in
this
>case?

It depends what you mean by 'can cause problems' One of the prime uses
of exceptions is to minimise damage caused by failed construction. Put
it another way, any problems will be less than those you get by not
using exceptions to report failure of a ctor.

Yes dtors are called for all bases and members that have been
constructed prior to theexception being thrown.

Francis Glassborow Chair of Association of C & C++ Users
64 Southfield Rd
Oxford OX4 1PA +44(0)1865 246490
All opinions are mine and do not represent those of any organisation

Siemel Naran

unread,
Jan 12, 1999, 3:00:00 AM1/12/99
to
On 12 Jan 1999 14:15:15 -0500, Valentin Bonnard <bonn...@pratique.fr>
wrote:
>Michael Huddlestone wrote:

>> I've heard that throwing exceptions from constructors can cause
problems.
>

>Utter non sens.

Not quite. Throwing an exception from a constructor might mean that
dynamic memory allocated in the constructor is not freed. We just
have to remember to catch exceptions the constructor may throw, free
the dynamic memory, and then rethrow. Or we mau use std::auto_ptr
member variables, where deletion of the pointed to objects is
automatic.


>OTOH, using exceptions is never trivial (see previous GotW).

This previous thread tackled exceptions arising from destructors,
which is a related issue every programmer ought to know about.

BTW, GotW are at "www.cntc.com/resources"

--
----------------------------------
Siemel B. Naran (sbn...@uiuc.edu)
----------------------------------

Herb Sutter

unread,
Jan 12, 1999, 3:00:00 AM1/12/99
to
On 12 Jan 1999 07:56:39 -0500, "Michael Huddlestone"
<m...@netcomuk.co.uk> wrote:
>I've heard that throwing exceptions from constructors can cause problems.
>Can anyone confirm/deny/expand on this? Is the destructor called in this
>case?

Destructors are only called on fully-constructed objects.

<shameless-plug>
Scott Meyers' "Effective C++ CD" has just come out. On it you will
find some Items on exceptions, but (ahem, more specifically) my
articles on writing exception-safe template classes. One of the issues
I expand on is the issues surrounding exceptions thrown from
constructors and destructors.
</shameless-plug>

Herb


---
Herb Sutter (mailto:hsu...@peerdirect.com)

PeerDirect Inc. 2695 North Sheridan Way, Suite 150
www.peerdirect.com Mississauga Ontario Canada L5K 2N6

Andrei Alexandrescu

unread,
Jan 12, 1999, 3:00:00 AM1/12/99
to
Michael Huddlestone wrote in message <01be3da4$b05f0260$59e92ac2@pppmdh>...

>I've heard that throwing exceptions from constructors can cause problems.


The only problem can be that your destructor will NOT be called. Destructors
are called only for fully-constructed objects. So if you throw an exception
from a constructor, be sure you cleanup whatever you had already
initialized.

This manual cleanup does not apply to base classes and members that take
care of their own cleanup (like smart pointers, strings, vectors). These
objects were either fully built (so they'll be destroyed) or not at all.

Throwing from a constructor is a very good technique, much better than the
pre-exceptions "construct-then-init" paradigm.

Andrei

Stanley Friesen [Contractor]

unread,
Jan 13, 1999, 3:00:00 AM1/13/99
to
In article <01be3da4$b05f0260$59e92ac2@pppmdh>,

Michael Huddlestone <m...@netcomuk.co.uk> wrote:
>I've heard that throwing exceptions from constructors can cause problems.
>Can anyone confirm/deny/expand on this?

AFAIK, not in a *conforming* implementation, unless one uses raw pointers
to allocated memory inside the object. But in some older implementations
it may potentially cause memory leaks and unreleased locks and the like.

Note, it is important, nay vital, that one use the "resource allocation
is initialization" paradigm within any constructor that may throw an
exception. See below for more details.

> Is the destructor called in this
>case?

No, but destructors for fully constructed *sub*objects (including base
classes) *are* called, at least in a conformin implementation.

This means as long as all resources are allocated using subobjects with
a proper destructor, all resources will be freed properly. Otherwise
there my be leaks.

Steve Clamage

unread,
Jan 14, 1999, 3:00:00 AM1/14/99
to
"Andrei Alexandrescu" <alexan...@micromodeling.com> writes:

>Michael Huddlestone wrote in message <01be3da4$b05f0260$59e92ac2@pppmdh>...

>>I've heard that throwing exceptions from constructors can cause problems.

>The only problem can be that your destructor will NOT be called. Destructors
>are called only for fully-constructed objects. So if you throw an exception
>from a constructor, be sure you cleanup whatever you had already
>initialized.

On the other hand, if the constructor encounters an error condition
and cannot finish its work, but does NOT exit via an exception,
the destructor will be called automatically when the object goes out
of scope (or is deleted). Destroying the incompletely-constructed
object may have its own set of problems.

To guard against those problems, you'd have to keep track in
each constructor of whether the things the destructor cares about
were completed, then test for those conditions in the destructor.

I'd say it's simpler to exit the constructor via an exception,
ensuring the destructor won't be automatically called.

--
Steve Clamage, stephen...@sun.com

John Nagle

unread,
Jan 14, 1999, 3:00:00 AM1/14/99
to
sta...@West.Sun.COM (Stanley Friesen [Contractor]) writes:
>In article <01be3da4$b05f0260$59e92ac2@pppmdh>,
>Michael Huddlestone <m...@netcomuk.co.uk> wrote:
>>I've heard that throwing exceptions from constructors can cause problems.
>>Can anyone confirm/deny/expand on this?

>AFAIK, not in a *conforming* implementation, unless one uses raw pointers
>to allocated memory inside the object. But in some older implementations
>it may potentially cause memory leaks and unreleased locks and the like.

It's supposed to work, and I don't know of any C++ implementation
currently on the market that gets it wrong. It's quite normal to
throw an exception in a constructor. This is one of the features of
C++ that was designed right.

Read up on what it means to throw an exception in a constructor,
though. You need to write exception-safe constructors, which is
usually easy.

Throwing an exception in a DEstructor is generally troublesome,
because you may end up in difficult exception-in-exception situations.

John Nagle

Paul Grealish

unread,
Jan 14, 1999, 3:00:00 AM1/14/99
to
Andrei Alexandrescu wrote:
>
> (snip)

>
> Throwing from a constructor is a very good technique, much better than
the
> pre-exceptions "construct-then-init" paradigm.

Could you provide some justification for the
above statement.

Given:
struct A { bool init(void); };

I cannot see why:
try
{
A a;
}
catch (...)
{
cerr << "Error constructing an A instance" << endl;
}

is superior to:
A a;
if ( !a.init() )
cerr << "Error constructing an A instance" << endl;

--
+---------------------------------+
| Paul Grealish |
| GEOPAK-TMS Limited |
| Cambridge, England |
| paul.g...@uk.geopak-tms.com |
+---------------------------------+

Pete Becker

unread,
Jan 14, 1999, 3:00:00 AM1/14/99
to
Siemel Naran wrote:
>
> On 12 Jan 1999 14:15:15 -0500, Valentin Bonnard <bonn...@pratique.fr>
> wrote:
> >Michael Huddlestone wrote:
>
> >> I've heard that throwing exceptions from constructors can cause
> problems.
> >
> >Utter non sens.
>
> Not quite. Throwing an exception from a constructor might mean that
> dynamic memory allocated in the constructor is not freed.

But that's not unique to destructors. It's a risk that we have to be
aware of in all of our code.

--
Pete Becker
Dinkumware, Ltd.
http://www.dinkumware.com

David Abrahams

unread,
Jan 14, 1999, 3:00:00 AM1/14/99
to
On 13 Jan 1999 20:43:48 -0500, sta...@West.Sun.COM (Stanley Friesen
[Contractor]) wrote:

>Note, it is important, nay vital, that one use the "resource allocation
>is initialization" paradigm within any constructor that may throw an
>exception. See below for more details.

I think that's a bit strong. I'd say it is "helpful and less
error-prone". Using try/catch to manage resources in constructors
works perfectly well also.

-Dave

sand...@my-dejanews.com

unread,
Jan 14, 1999, 3:00:00 AM1/14/99
to
In article <01be3da4$b05f0260$59e92ac2@pppmdh>,

"Michael Huddlestone" <m...@netcomuk.co.uk> wrote:
> I've heard that throwing exceptions from constructors can cause problems.
> Can anyone confirm/deny/expand on this? Is the destructor called in this
> case?
>
> Thanks, Mike.

>
> [ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
> [ about comp.lang.c++.moderated. First time posters: do this! ]
>

Read Scott Meyers "More Effective C++" Item 10, quote "destructor will never
called. Never. C++ only destroys fully constructed objects"


-----------== Posted via Deja News, The Discussion Network ==----------
http://www.dejanews.com/ Search, Read, Discuss, or Start Your Own

sand...@my-dejanews.com

unread,
Jan 14, 1999, 3:00:00 AM1/14/99
to

Hyman Rosen

unread,
Jan 14, 1999, 3:00:00 AM1/14/99
to
Paul Grealish wrote:
> Given:
> struct A { bool init(void); };
>
> I cannot see why:
> try { A a; }
> catch (...) { cerr << "Error constructing an A instance" <<
endl; }
> is superior to:
> A a;
> if ( !a.init() )
> cerr << "Error constructing an A instance" << endl;

Because the latter version requires the check at every single point that
you attempt to create an A object. In the former case, there is no need
to provide a try-catch block around each A. The handler can be in some
outer subroutine, while the inner code goes ahead and uses A objects as
if they can never fail construction.

Rob Richardson

unread,
Jan 14, 1999, 3:00:00 AM1/14/99
to
Paul Grealish wrote, in part:

>Given:
> struct A { bool init(void); };
>
>I cannot see why:
> try
> {
> A a;
> }
> catch (...)
> {
> cerr << "Error constructing an A instance" << endl;
> }
>
>is superior to:
> A a;
> if ( !a.init() )
> cerr << "Error constructing an A instance" << endl;

I would think the ability to trap an exception during an invisible
object
instantiation would be quite valuable. I'm not quite sure what term I
should use for "invisible", so here's an example:

class Foo { /* details omitted */ };

void foo(Foo x) {}

int main()
{
Foo a;
foo(a);
return 0;
}

In this case, a copy of a is constructed and passed to the function
foo().
There is no opportunity to call an init() function. If the construction
of
the copy fails, an exception could handle the failure.
--
Rob Richardson
ELSAG BAILEY, INC.
Opinions are my own, and do not necessarily represent those of my
employer.

Pete Becker

unread,
Jan 15, 1999, 3:00:00 AM1/15/99
to
Pete Becker wrote:
>
>
> But that's not unique to destructors.

Of course, the last word in that sentence should have been
"constructors".

--
Pete Becker
Dinkumware, Ltd.
http://www.dinkumware.com

[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]

Stanley Friesen [Contractor]

unread,
Jan 15, 1999, 3:00:00 AM1/15/99
to
In article <01be3da4$b05f0260$59e92ac2@pppmdh>,
Michael Huddlestone <m...@netcomuk.co.uk> wrote:
>I've heard that throwing exceptions from constructors can cause problems.
>Can anyone confirm/deny/expand on this?

AFAIK, not in a *conforming* implementation, unless one uses raw pointers


to allocated memory inside the object. But in some older implementations
it may potentially cause memory leaks and unreleased locks and the like.

Note, it is important, nay vital, that one use the "resource allocation


is initialization" paradigm within any constructor that may throw an
exception. See below for more details.

> Is the destructor called in this
>case?

No, but destructors for fully constructed *sub*objects (including base


classes) *are* called, at least in a conformin implementation.

This means as long as all resources are allocated using subobjects with
a proper destructor, all resources will be freed properly. Otherwise
there my be leaks.

[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]

Steve Clamage

unread,
Jan 15, 1999, 3:00:00 AM1/15/99
to
"Andrei Alexandrescu" <alexan...@micromodeling.com> writes:

>Michael Huddlestone wrote in message <01be3da4$b05f0260$59e92ac2@pppmdh>...

>>I've heard that throwing exceptions from constructors can cause problems.

>The only problem can be that your destructor will NOT be called. Destructors
>are called only for fully-constructed objects. So if you throw an exception
>from a constructor, be sure you cleanup whatever you had already
>initialized.

On the other hand, if the constructor encounters an error condition
and cannot finish its work, but does NOT exit via an exception,
the destructor will be called automatically when the object goes out
of scope (or is deleted). Destroying the incompletely-constructed
object may have its own set of problems.

To guard against those problems, you'd have to keep track in
each constructor of whether the things the destructor cares about
were completed, then test for those conditions in the destructor.

I'd say it's simpler to exit the constructor via an exception,
ensuring the destructor won't be automatically called.

--
Steve Clamage, stephen...@sun.com

[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]

John Nagle

unread,
Jan 15, 1999, 3:00:00 AM1/15/99
to
sta...@West.Sun.COM (Stanley Friesen [Contractor]) writes:
>In article <01be3da4$b05f0260$59e92ac2@pppmdh>,
>Michael Huddlestone <m...@netcomuk.co.uk> wrote:
>>I've heard that throwing exceptions from constructors can cause problems.
>>Can anyone confirm/deny/expand on this?

>AFAIK, not in a *conforming* implementation, unless one uses raw pointers
>to allocated memory inside the object. But in some older implementations
>it may potentially cause memory leaks and unreleased locks and the like.

It's supposed to work, and I don't know of any C++ implementation


currently on the market that gets it wrong. It's quite normal to
throw an exception in a constructor. This is one of the features of
C++ that was designed right.

Read up on what it means to throw an exception in a constructor,
though. You need to write exception-safe constructors, which is
usually easy.

Throwing an exception in a DEstructor is generally troublesome,
because you may end up in difficult exception-in-exception situations.

John Nagle

[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]

Paul Grealish

unread,
Jan 15, 1999, 3:00:00 AM1/15/99
to
Andrei Alexandrescu wrote:
>
> (snip)
>
> Throwing from a constructor is a very good technique, much better than
the
> pre-exceptions "construct-then-init" paradigm.

Could you provide some justification for the
above statement.

Given:
struct A { bool init(void); };

I cannot see why:
try
{
A a;
}
catch (...)
{
cerr << "Error constructing an A instance" << endl;
}

is superior to:
A a;
if ( !a.init() )
cerr << "Error constructing an A instance" << endl;

--

+---------------------------------+
| Paul Grealish |
| GEOPAK-TMS Limited |
| Cambridge, England |
| paul.g...@uk.geopak-tms.com |
+---------------------------------+

[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]

Alexey N. Solofnenko

unread,
Jan 15, 1999, 3:00:00 AM1/15/99
to

Hello!

> sand...@my-dejanews.com wrote:
>
> Read Scott Meyers "More Effective C++" Item 10, quote "destructor
will never
> called. Never. C++ only destroys fully constructed objects"


Yes, you (and Meyers) are right, but parents' and successfully created
members'
destructors will be called. So there is no big problem - only if you
write
something like :

Obj::Obj() : m_arr1(new int[100]),m_arr2(new int[100]) {}

if the second new fails the first one will not be deallocated if general
pointers are used.

Best wishes,
Alexey Solofnenko.

Pete Becker

unread,
Jan 15, 1999, 3:00:00 AM1/15/99
to
Siemel Naran wrote:
>
> On 12 Jan 1999 14:15:15 -0500, Valentin Bonnard <bonn...@pratique.fr>
> wrote:
> >Michael Huddlestone wrote:
>
> >> I've heard that throwing exceptions from constructors can cause
> problems.
> >
> >Utter non sens.
>
> Not quite. Throwing an exception from a constructor might mean that
> dynamic memory allocated in the constructor is not freed.

But that's not unique to destructors. It's a risk that we have to be
aware of in all of our code.

--

Pete Becker
Dinkumware, Ltd.
http://www.dinkumware.com

[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]

David Abrahams

unread,
Jan 15, 1999, 3:00:00 AM1/15/99
to
On 13 Jan 1999 20:43:48 -0500, sta...@West.Sun.COM (Stanley Friesen
[Contractor]) wrote:

>Note, it is important, nay vital, that one use the "resource allocation
>is initialization" paradigm within any constructor that may throw an
>exception. See below for more details.

I think that's a bit strong. I'd say it is "helpful and less


error-prone". Using try/catch to manage resources in constructors
works perfectly well also.

-Dave

[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]

sand...@my-dejanews.com

unread,
Jan 15, 1999, 3:00:00 AM1/15/99
to
In article <01be3da4$b05f0260$59e92ac2@pppmdh>,

"Michael Huddlestone" <m...@netcomuk.co.uk> wrote:
> I've heard that throwing exceptions from constructors can cause problems.
> Can anyone confirm/deny/expand on this? Is the destructor called in this
> case?
>
> Thanks, Mike.

>
> [ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
> [ about comp.lang.c++.moderated. First time posters: do this! ]
>

Read Scott Meyers "More Effective C++" Item 10, quote "destructor will never


called. Never. C++ only destroys fully constructed objects"

-----------== Posted via Deja News, The Discussion Network ==----------
http://www.dejanews.com/ Search, Read, Discuss, or Start Your Own

[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]

Hyman Rosen

unread,
Jan 15, 1999, 3:00:00 AM1/15/99
to
Paul Grealish wrote:
> Given:
> struct A { bool init(void); };
>
> I cannot see why:
> try { A a; }
> catch (...) { cerr << "Error constructing an A instance" <<
endl; }
> is superior to:
> A a;
> if ( !a.init() )
> cerr << "Error constructing an A instance" << endl;

Because the latter version requires the check at every single point that


you attempt to create an A object. In the former case, there is no need
to provide a try-catch block around each A. The handler can be in some
outer subroutine, while the inner code goes ahead and uses A objects as
if they can never fail construction.

[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]

sand...@my-dejanews.com

unread,
Jan 15, 1999, 3:00:00 AM1/15/99
to
In article <01be3da4$b05f0260$59e92ac2@pppmdh>,
"Michael Huddlestone" <m...@netcomuk.co.uk> wrote:
> I've heard that throwing exceptions from constructors can cause
problems.
> Can anyone confirm/deny/expand on this? Is the destructor called in
this
> case?
>
> Thanks, Mike.
>
> [ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
> [ about comp.lang.c++.moderated. First time posters: do this! ]
>

Read Scott Meyers "More Effective C++" Item 10, quote "destructor will


never
called. Never. C++ only destroys fully constructed objects"


-----------== Posted via Deja News, The Discussion Network ==----------
http://www.dejanews.com/ Search, Read, Discuss, or Start Your Own

Jerry Coffin

unread,
Jan 16, 1999, 3:00:00 AM1/16/99
to
In article <369DE4...@uk.geopak-tms.com>, paul.g...@uk.geopak-
tms.com says...

> Andrei Alexandrescu wrote:
> >
> > (snip)
> >
> > Throwing from a constructor is a very good technique, much better than
> the
> > pre-exceptions "construct-then-init" paradigm.
>
> Could you provide some justification for the
> above statement.
>
> Given:
> struct A { bool init(void); };
>
> I cannot see why:
> try
> {
> A a;
> }
> catch (...)
> {
> cerr << "Error constructing an A instance" << endl;
> }
>
> is superior to:
> A a;
> if ( !a.init() )
> cerr << "Error constructing an A instance" << endl;

If this was all you were doing and A didn't support any other
operations, then no, it probably wouldn't be superior, or at least not
enough to notice. Then again, if A doesn't do more than that, it's
not worth writing the code for A, or creating an instance of it
either.

However, even in this case the problem still exists -- it's possible
(and often easy) to create an A object _without_ initializing it if
the initialization happens separately from the construction. Now, if
the code above was the only kind of circumstance under which you'd
ever create objects of type A, then you probably wouldn't have to
worry much. However, consider somebody adding:

A operator+(A &a, A&b) {
return [ something or other ] ;
}

now, for code that uses this to work correctly, we have to be able to
add `a' and `b' together, and create a temporary A to hold the result,
BUT the compiler doesn't know that before it can reasonably use that
temporary to hold the result that it has to call init(). The compiler
knows that the ctor for A has to be called, but not about any other
initialization functions.

Also note that somebody else entirely can write code to overload
operators on your type LONG after you've completed your work on your
class. If your class is well-written and resilient, it'll be able to
handle this with relative aplomb. If your class requires extra
initializer functions to be called before objects can be put to use,
it'll almost certainly break when they do something like this.

The same happens when/if somebody derives a class from your's --
again, some ctor will always be used to construct your class' sub-
object part of the derived class. There compiler is written to
automatically ensure that this happens. If, however, you require some
other initialization function to be called, this will NOT happen
automatically.

One of the most basic goals of OO programming is to minimize the long-
term cost of the code, which mostly means the cost of maintaining the
code. If you require a special initializer function to be called
before objects of your class are in a usable state, you place the
burden of making that function call on all users of your class. If,
however, you do all initialization in the ctor, you make it possible
for everybody who uses your class to ignore most issues related to
initialization. About the only thing they're left with is the
possible requirement to supply arguments to your ctors, which is about
as minimal and non-intrusive as you can hope for.

In the final analysis, you can't take an example like the one above,
and make any sort of meaningful comparison based on it. You've got to
start by looking at real classes put into real uses before you can get
anything meaningful at all. You have to continue by looking at how
those classes are put to use in real, live programs before they have
any hope of meaning. You conclude by (hopefully) looking at the
mistakes others have made rather than insisting on foisting second-
rate designs off on the world before you'll believe they have
problems.

If you truly believe that two-step initialization isn't a problem, I'd
advise looking in the MFC newsgroups for a while, to see the amount of
real-world headache they really cause. In fairness, I should add that
in some cases, it's not possible to combine construction with complete
initialization without either warping your classes considerably, or
otherwise causing other problems. If that's the case, you do the best
you can and live with the consequences. Despite this, I, for one, am
convinced that it's best to shoot for as complete of initialization as
possible in the ctor, and minimizing (and normally eliminating) any
need for other initialization functions being called (of course,
that's not to say there's anything wrong with having an initialization
function that's used by a number of ctors to do parts of their work --
that's an entirely different situation).

Paul Grealish

unread,
Jan 16, 1999, 3:00:00 AM1/16/99
to
Rob Richardson wrote:
>
> Paul Grealish wrote, in part:
> >Given:
> > struct A { bool init(void); };
> >
> >I cannot see why:
> > try
> > {
> > A a;
> > }
> > catch (...)
> > {
> > cerr << "Error constructing an A instance" << endl;
> > }
> >
> >is superior to:
> > A a;
> > if ( !a.init() )
> > cerr << "Error constructing an A instance" << endl;
>
> I would think the ability to trap an exception during an invisible
> object
> instantiation would be quite valuable. I'm not quite sure what term I
> should use for "invisible"

"temporary" - only possible if the class provides a
copy constructor. I get around this issue, by not
providing copy and assign for 'multi-state' classes.
I use 'multi-state' to describe a class that has
explicit methods to acquire resources.
If the class does need to provide copy/assign, I provide
them as named functions that have the ability to
return an error rather than simply providing a copy
constructor and assignment operator. I've found the
approach makes for more readable / maintainable code.

--
+---------------------------------+
| Paul Grealish |
| GEOPAK-TMS Limited |
| Cambridge, England |
| paul.g...@uk.geopak-tms.com |
+---------------------------------+

[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]

Francis Glassborow

unread,
Jan 16, 1999, 3:00:00 AM1/16/99
to
In article <369E1D7C...@prolifics.com>, Hyman Rosen
<hy...@prolifics.com> writes

>> Given:
>> struct A { bool init(void); };
>>
>> I cannot see why:
>> try { A a; }
>> catch (...) { cerr << "Error constructing an A instance" <<
>endl; }
>> is superior to:
>> A a;
>> if ( !a.init() )
>> cerr << "Error constructing an A instance" << endl;
>
>Because the latter version requires the check at every single point that
>you attempt to create an A object. In the former case, there is no need
>to provide a try-catch block around each A. The handler can be in some
>outer subroutine, while the inner code goes ahead and uses A objects as
>if they can never fail construction.

In addition this is a very simple piece of code, but what happens if the
A is either a base object or a member of some other class? The
resulting init() functions become ever more complicated and the chance
that the resulting large scale object is constructed in a destructable
form gets ever smaller.

Do it right for simple cases and then the complicated ones take care of
themselves.

Francis Glassborow Chair of Association of C & C++ Users
64 Southfield Rd
Oxford OX4 1PA +44(0)1865 246490
All opinions are mine and do not represent those of any organisation

Stanley Friesen [Contractor]

unread,
Jan 16, 1999, 3:00:00 AM1/16/99
to
In article <nagleF5...@netcom.com>, John Nagle <na...@netcom.com> wrote:

>sta...@West.Sun.COM (Stanley Friesen [Contractor]) writes:
>
> It's supposed to work, and I don't know of any C++ implementation
>currently on the market that gets it wrong. It's quite normal to
>throw an exception in a constructor. This is one of the features of
>C++ that was designed right.

I seem to remember some early compilers with exception support that failed
to release memory an object allocated by new() when *its* constructor
threw an exception.

As you say, the standard makes the correct requirement here. I am just not
sure ALL older compilers got it right.


>
> Read up on what it means to throw an exception in a constructor,
>though. You need to write exception-safe constructors, which is
>usually easy.

Quite. It is also no different than making any other function
exception safe.


>
> Throwing an exception in a DEstructor is generally troublesome,
>because you may end up in difficult exception-in-exception situations.

I generally disrecommend that.

Valentin Bonnard

unread,
Jan 17, 1999, 3:00:00 AM1/17/99
to
Siemel Naran wrote:
>
> On 12 Jan 1999 14:15:15 -0500, Valentin Bonnard <bonn...@pratique.fr>
> wrote:
> >Michael Huddlestone wrote:
>
> >> I've heard that throwing exceptions from constructors can cause
> >> problems.
> >
> >Utter non sens.
>
> Not quite. Throwing an exception from a constructor might mean that
> dynamic memory allocated in the constructor is not freed.

Just as in any other function.

Authors who say that it's more dangerous to throw an exception
in a ctor than anywhere else simply don't understand the way
exceptions work (and I have to put Bjarne in this list - sorry).

Ressource leaks are an issue in every function in which exceptions
may be thrown. Class invariants are an issue in allmost every non
const function except the ctor; thus a ctor is easier to make
exception safe than most functions.

Note that during the execution of a ctor, class members should be
treated as function locals by the programmer.

Again: writing exception safe code is more difficult than 'normal'
code, and it's something that you don't see until your computer
crash (just as correct use of delete or out of bound accesses).

Also exception safety is completly ignored by many (all ?) style
guides, by many programmers, and exceptions are considered safe
by design. (And this problem concerns C++, Java, Ada, or ML.)

I think that marketing is partially responsible for that (come
on! program in Java! High level language with exceptions! Easier
to use than return codes! No pitfalls! Exceptions are the
Superior Solution TM! Exceptions make error handling easy even
for stupid programmers! etc)

--

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

Siemel Naran

unread,
Jan 18, 1999, 3:00:00 AM1/18/99
to
On 17 Jan 1999 10:13:45 -0500, Valentin Bonnard <bonn...@pratique.fr> wrote:

[Michael Huddlestone wrote]
>> >> I've heard that throwing exceptions from constructors can cause
>> >> problems.

[Valentin Bonnard]
>> >Utter non sens.

[Siemel Naran wrote]


>> Not quite. Throwing an exception from a constructor might mean that
>> dynamic memory allocated in the constructor is not freed.


>Just as in any other function.

Sorry, I must rephrase. What I mean is that the destructor for
the object is not automatically called, so dynamic memory
won't be automatically deleted. Eg,

class X {
private:
int * i1;
int * i2;
public:
explicit X(int x_) { i1=new int(x_); i2=new int(x_); }
X(const X&);
X& operator=(const X&);
~X() { delete i2; delete i1; }
};

What happens if we do
void f() { X x(0); }
and there is an error while constructing 'i2'. The first guess, at
least for someone new to exceptions, is that exception, X::~X() is
called to clean up the mess. Hence, from this point of view, the
above class X is exception safe.

But X::~X() can't be called as this would, in general, delete
unconstructed objects. For example,

class X {
private:
Thing t1;
int * i1;
int * i2;
Thing t1;
public:
explicit X(int x_) { i1=new int(x_); i2=new int(x_); }
X(const X&);
X& operator=(const X&);
~X() { delete x; }
};

Suppose there is an error constructing X::t1. Now what happens if
X::~X() is called? In full, this destructor is
X::~X() { delete i2; delete i1} : ~t2(), ~i2(), ~i1(), ~t1()
We would call "delete i2", and as 'i2' doesn't point to anything,
we get a program crash. Similar remarks for 'i1'. Then we delete
't2' by calling Thing::~Thing(). But 't2' was never constructed,
so third program crash is imminent. Then the pointer 'i2' is
deleted; this is harmless as we are deleting the pointer itself,
not the pointed to object. Similar for 'i1'. Then we delete the
unconstructed 't1', which is a fourth program crash.

So the rule is that the destructor X::~X() is not called. And
I don't think this is immediately obvious to anyone new to
exceptions. So the exception safe class X is:

class X {
private:
int * i1;
int * i2;
public:
explicit X(int x_) {
try { i1=new int(x_); }
catch (std::bad_alloc&) { throw; }
try { i2=new int(x_); }
catch (std::bad_alloc&) { delete i1; }
}
X(const X&);
X& operator=(const X&);
~X() { delete i2; delete i1; }
};


The use of repeated try-catch blocks is necessary, but it is
very tedious to look at. So instead we use std::auto_ptr.

class X {
private:
std::auto_ptr<int> i1;
std::auto_ptr<int> i2;
public:
explicit X(int x_) : i1(new int(x_)), i2(new int(x_)) { }
X(const X&);
X& operator=(const X&);
~X() { delete i2; delete i1; }
};


>Authors who say that it's more dangerous to throw an exception
>in a ctor than anywhere else simply don't understand the way
>exceptions work (and I have to put Bjarne in this list - sorry).

I'm sure these people are talking about the fact that you have
to use multiple try-catch blocks, as above, or use std::auto_ptr.

>Ressource leaks are an issue in every function in which exceptions
>may be thrown. Class invariants are an issue in allmost every non
>const function except the ctor; thus a ctor is easier to make
>exception safe than most functions.

Class invariants are important in constructors too, as when you
construct an object from the state of two other objects. An
example is our operator constructor, eg
class X {
struct ADD { };
X(const X& lhs, const X& rhs, ADD);
// stuff
};

Second, class invariants are also important in const member
functions. This is because some member variables may be
'mutable', and thus they may be changed, even in a non-const
function. This was one of my reasons to avoid potentially
dangerous constructs like 'mutable' whenever possible. Also,
pointer to implementation variables are pretty much like
'mutable' too.


>Note that during the execution of a ctor, class members should be
>treated as function locals by the programmer.

Don't know what you mean.


>Again: writing exception safe code is more difficult than 'normal'
>code, and it's something that you don't see until your computer
>crash (just as correct use of delete or out of bound accesses).
>
>Also exception safety is completly ignored by many (all ?) style
>guides, by many programmers, and exceptions are considered safe
>by design. (And this problem concerns C++, Java, Ada, or ML.)
>
>I think that marketing is partially responsible for that (come
>on! program in Java! High level language with exceptions! Easier
>to use than return codes! No pitfalls! Exceptions are the
>Superior Solution TM! Exceptions make error handling easy even
>for stupid programmers! etc)

Fine. (Got to keep the stock price up, right :)).

--
----------------------------------
Siemel B. Naran (sbn...@uiuc.edu)
----------------------------------

Paul Grealish

unread,
Jan 18, 1999, 3:00:00 AM1/18/99
to
Hyman Rosen wrote:

>
> Paul Grealish wrote:
> > Given:
> > struct A { bool init(void); };
> >
> > I cannot see why:
> > try { A a; }
> > catch (...) { cerr << "Error constructing an A instance" <<
> endl; }
> > is superior to:
> > A a;
> > if ( !a.init() )
> > cerr << "Error constructing an A instance" << endl;
>
> Because the latter version requires the check at every single point that
> you attempt to create an A object. In the former case, there is no need
> to provide a try-catch block around each A. The handler can be in some
> outer subroutine, while the inner code goes ahead and uses A objects as
> if they can never fail construction.

Agreed - it's a design issue. I reserve exceptions
for truly exceptional conditions - not for failure
in constructor code that could easily be predicted.
If the 'init' code has a failure probability of 1%
or more, I provide an explicit method. Of course, I
don't really know what the probability is of the
'init' code failing. I look at what needs to be done
and ask myself: "does it look like there's any
reasonable way that that bit of constructor code could
fail?" If the answer's 'yes' I provide an explicit
method. Works well for me.


--
+---------------------------------+
| Paul Grealish |
| GEOPAK-TMS Limited |
| Cambridge, England |
| paul.g...@uk.geopak-tms.com |
+---------------------------------+

[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]

Bjarne Stroustrup

unread,
Jan 18, 1999, 3:00:00 AM1/18/99
to

> Authors who say that it's more dangerous to throw an exception
> in a ctor than anywhere else simply don't understand the way
> exceptions work (and I have to put Bjarne in this list - sorry).

What am I supposed to have have I done to be labelled as "simply
don't understand the way exceptions work?"

- Bjarne
Bjarne Stroustrup - http://www.research.att.com/~bs

Bruce DeVisser

unread,
Jan 18, 1999, 3:00:00 AM1/18/99
to
On 18 Jan 1999 00:14:04 -0500, Siemel Naran wrote:
> The use of repeated try-catch blocks is necessary, but it is
> very tedious to look at. So instead we use std::auto_ptr.
>
> class X {
> private:
> std::auto_ptr<int> i1;
> std::auto_ptr<int> i2;
> public:
> explicit X(int x_) : i1(new int(x_)), i2(new int(x_)) { }
> X(const X&);
> X& operator=(const X&);
> ~X() { delete i2; delete i1; }
> };

X::~X() {}
... lest you deallocate one time too many.

Bruce DeVisser
b...@torcon.com

Valentin Bonnard

unread,
Jan 19, 1999, 3:00:00 AM1/19/99
to
Bjarne Stroustrup wrote:
>
> > Authors who say that it's more dangerous to throw an exception
> > in a ctor than anywhere else simply don't understand the way
> > exceptions work (and I have to put Bjarne in this list - sorry).
>
> What am I supposed to have have I done to be labelled as "simply
> don't understand the way exceptions work?"

It was perhaps a bit exagerated, but:

You have written that it is a bad in idea to throw from a
copy ctor. It isn't a bad idea at all; it's even a very
common and safe practice (copy ctors often have to allocate
memory and may throw bad_alloc).

A copy ctor should never modify external objects (except
perhaps for mutable state), it should only change *this.
So if it is aborted, it leaves no external objects in an
undefined state.

A class with a copy ctor which throws can safely be used
with the STL (has the value_type for a Containner).

I have read in many places that it's a bad idea to throw
from a ctor. This feeling comes from a poor understanding
(or no understanding at all) of C++ exceptions, probably
a surprise: the dtor isn't called (which would be a terrible
thing), or various religious fears. (Exceptions are surounded
by both religious (or it is commercial ?) hype and religious
fears, and misunderstandings - and all these are equaly bad.)

(It's in C++PL 3rd, in the one the advices lists - I want to
add that I think that this book is very good, except for a few
details. I often recommand it.)

--

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

[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]

Gargantua Blargg

unread,
Jan 21, 1999, 3:00:00 AM1/21/99
to
In article <77oic4$e...@netlab.cs.rpi.edu>, abra...@spam.motu.com wrote:

> On 13 Jan 1999 20:43:48 -0500, sta...@West.Sun.COM (Stanley Friesen
> [Contractor]) wrote:
>
> >Note, it is important, nay vital, that one use the "resource allocation
> >is initialization" paradigm within any constructor that may throw an
> >exception. See below for more details.
>
> I think that's a bit strong. I'd say it is "helpful and less
> error-prone". Using try/catch to manage resources in constructors
> works perfectly well also.

It's kind of funny. First, we had error codes with error handling code
sprinkled on the statement level. Then exceptions came along, and their
advantage was the separation of error handling code on the block level
(loose definition of block here). But I have found this to be error-prone
and a maintenance problem (duplicated code). I've found it's best to have
objects own items on the freestore at all times, and have very controlled
transfer of ownership in an exception safe way. So we move away from
having catch blocks that perform cleanup (in general).

I don't think using try/catch to manage resources in constructors, or
anywhere else, "works perfectly well". Code that does this is harder to
understand, and insure works correctly by looking at the code, and gets
out of hand very quickly:

X* x1 = new X;

// ... (may throw)

try
{
X* x2 = new X;

// ... (may throw)

try
{
X* x3 = new X;

// ... (this better not throw, or yet another try block!)

delete x3;
}
catch ( ... )
{
delete x2;
throw;
}
}
catch ( ... )
{
delete x1;
throw;
}

delete x2;
delete x1; // I almost forgot to add these!

Oh, and the auto_ptr version (laugh)

auto_ptr<X> x1( new X );

// ... (may throw)

auto_ptr<X> x2( new X );

// ... (may throw)

auto_ptr<X> x3( new X );

// ... (may throw)

hahahahahahahaha (I love progress!)

--
"I don't like my edges rounded off" - Ani DiFranco

Gargantua Blargg | bla...@flash.net | http://www.flash.net/~blargg/

Bjarne Stroustrup

unread,
Jan 22, 1999, 3:00:00 AM1/22/99
to
Valentin Bonnard <bonn...@pratique.fr> writes:

> Bjarne Stroustrup wrote:
> >
> > > Authors who say that it's more dangerous to throw an exception
> > > in a ctor than anywhere else simply don't understand the way
> > > exceptions work (and I have to put Bjarne in this list - sorry).
> >

> > What am I supposed to have done to be labelled as "simply


> > don't understand the way exceptions work?"
>
> It was perhaps a bit exagerated, but:
>
> You have written that it is a bad in idea to throw from a
> copy ctor.

I'd say! I did not "say that it's more dangerous to throw an exception
in a ctor than anywhere else" yet you single me out among authors who
"simply don't understand the way exceptions work" because I "have


written that it is a bad in idea to throw from a copy ctor".

> It isn't a bad idea at all; it's even a very
> common and safe practice (copy ctors often have to allocate
> memory and may throw bad_alloc).
>
> A copy ctor should never modify external objects (except
> perhaps for mutable state), it should only change *this.
> So if it is aborted, it leaves no external objects in an
> undefined state.

> A class with a copy ctor which throws can safely be used
> with the STL (has the value_type for a Containner).

No. I think you are mistaken. For example, consider insert() into a
vector<X>. In the cases where you don't simply overwrite existing elements.
X's copy constructor is used to copy elements into the vector.
If X's copy constructor throws an exception for the single elemnent
insert() the container is guaranteed to be unchanged. That guarantee
is not provided for the multiple element insert(). Some elements (but
not all) may be inserted, maybe no elements are inserted, maybe an
element that is not a copy of an "inserted" element is inserted.
Maybe the vector is in a state where it cannot be destructed because
the copy loop was interrupted by an exception.

Similar problems can occur for some algorithms (such as sort) if
a copy constructor throws an exception.

For programs that are meant to be resillient against errors,
I'm willing to program very defensively to avoid this.

(PS. I consider the limited exception safety properties guaranteed by
the standard library reasonable and will eventually get around to
writing something to explain why and what the alternatives were).

> I have read in many places that it's a bad idea to throw
> from a ctor. This feeling comes from a poor understanding
> (or no understanding at all) of C++ exceptions,

But you have not read that in my writings. Please don't lump me with
unspecified authors with "a poor understanding (or no understanding
at all) of C++ exceptions."

One of the major reasons I designed exceptions was to get a clean way
of reporting errors in constructors. As you know, I recommend exceptions
as the way to deal with errors in constructors unless there are specific
reasons not to.

> probably
> a surprise: the dtor isn't called (which would be a terrible
> thing), or various religious fears. (Exceptions are surounded
> by both religious (or it is commercial ?) hype and religious
> fears, and misunderstandings - and all these are equaly bad.)

Nobody should be surprised that a destructor is not called for an
object that wasn't (completely) constructed - but undoubtedly someone
who hasn't understood the exception model could be surprised.
Even partially constructed objects are correctly partially destroyed.

> (It's in C++PL 3rd, in the one the advices lists - I want to
> add that I think that this book is very good, except for a few
> details. I often recommand it.)

Thanks.

The recommendation against throwing exceptions from copy constructors
is 14.11[9]:

Avoid throwing exceptions from copy constructors; 14.4.6.1.

Maybe "Avoid" could be read as "never under any circumstances". If so,
I should rephrase it.

Here is the quote from 14.4.6.1 referred to:

Copy constructors (10.4.4.1) are special in that they are
invoked implicitly and because they often both acquire and
release resources. In particular, the standard library
assumes proper - non-exception-throwing - behavior of copy
constructors. For these reasons, care should be taken that
a copy constructor throws an exception only in truly
disastrous circumstances. Complete recovery from an exception
in a copy constructor is unlikely to be feasible in every
context of its use. To be even potentially safe, a copy
constructor must leave behind two objects, each of which
fulfills the invariant of its class (24.3.7.1).

Naturally, copy assignment operators should be treated with
as much care as copy constructors.

Other useful advice on the use of exceptions can be found in Chapter 8,
Chapter 15, and summarized in their advice sections.

I think that constructors that are called by standard containers should
be treated with extra care compared to other constructors.
For the same reasons, we should take extra care with copy assignments.


- Bjarne
Bjarne Stroustrup - http://www.research.att.com/~bs

Stanley Friesen [Contractor]

unread,
Jan 22, 1999, 3:00:00 AM1/22/99
to
In article <slrn7a4iem....@localhost.localdomain>,

Siemel Naran <sbn...@uiuc.edu> wrote:
> explicit X(int x_) { i1=new int(x_); i2=new int(x_); }
> ...

>What happens if we do
> void f() { X x(0); }
>and there is an error while constructing 'i2'.

Bad things.

There are two possible solutions

1) make i1 and i2 auto_ptr's.

2) place a function try block on the constructor:
explicit X(int x_):i1(0), i2(0) try
{ i1=new int(x_); i2=new int(x_); }
catch(...) { delete i1; delete i2 };
[My syntax may be a touch off, I do not remember exactly where the
"try" keyword goes wrt the ctor-initializors].

Note, the intializers that NULL the member variables are important,
as otherwise the deletes in the catch may generate undefined behavior.

>The use of repeated try-catch blocks is necessary,


Not at all. Just pre-initialize the two pointers to NULL, and then use
*one* function try block. Deleting NULL is legal and defined to do nothing.

Matt Austern

unread,
Jan 22, 1999, 3:00:00 AM1/22/99
to
b...@research.att.com (Bjarne Stroustrup) writes:

> No. I think you are mistaken. For example, consider insert() into a
> vector<X>. In the cases where you don't simply overwrite existing elements.
> X's copy constructor is used to copy elements into the vector.
> If X's copy constructor throws an exception for the single elemnent
> insert() the container is guaranteed to be unchanged. That guarantee
> is not provided for the multiple element insert(). Some elements (but
> not all) may be inserted, maybe no elements are inserted, maybe an
> element that is not a copy of an "inserted" element is inserted.
> Maybe the vector is in a state where it cannot be destructed because
> the copy loop was interrupted by an exception.

It's possible, actually, to implement vector<T> so that it always
remains in a consistent state even if T's copy constructor throws an
exception during multiple-element insert, where by "consistent
state" I mean that the vector satisfies its class invariants and can
safely be destroyed. A little bit tricky, yes, but not impossible.

The key is that uninitialized_copy can easily be made commit-or-
rollback (either it succeeds or it does nothing), and that once you've
succeeded with the uninitialized_copy all you're doing is performing
assignments from one element of the vector to another.

I believe that Dave Abrahams and I have independently written versions
of vector<>::insert that satisfy this property. If you find that my
implementation of vector<> doesn't satisfy it, then you will have
found a serious bug that I'd like to know about.

sto...@profuse.ro

unread,
Jan 22, 1999, 3:00:00 AM1/22/99
to
In article <1999012017...@centurion.flash.net>,

bla...@flash.net (Gargantua Blargg) wrote:
> In article <77oic4$e...@netlab.cs.rpi.edu>, abra...@spam.motu.com wrote:
>
> [...]

> I don't think using try/catch to manage resources in constructors, or
> anywhere else, "works perfectly well". Code that does this is harder to
> understand, and insure works correctly by looking at the code, and gets
> out of hand very quickly:
>
> X* x1 = new X;
>
> // ... (may throw)
>
> try
> {
> X* x2 = new X;
>
> // ... (may throw)
>
> try
> {
> X* x3 = new X;
>
> // ... (this better not throw, or yet another try block!)
>
> delete x3;
> }
> catch ( ... )
> {
> delete x2;
> throw;
> }
> }
> catch ( ... )
> {
> delete x1;
> throw;
> }
>
> delete x2;
> delete x1; // I almost forgot to add these!

Your example is not showing that the code could get out of hand very quickly,
is just bad code, which could be easily be avoided:

try
{
X x1;
X x2;
X x3;
...
}
catch ( std::exception &e) // or something like that
{ //...
}

And of course, if you want using arrays, why not use the STL?
It's very good at taking the burden of array creation/ destruction/ handling
off of you.

John.

-----------== Posted via Deja News, The Discussion Network ==----------
http://www.dejanews.com/ Search, Read, Discuss, or Start Your Own

[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]

Dave Abrahams

unread,
Jan 22, 1999, 3:00:00 AM1/22/99
to

Gargantua Blargg wrote in message
<1999012017...@centurion.flash.net>...

>In article <77oic4$e...@netlab.cs.rpi.edu>, abra...@spam.motu.com wrote:
>
>> On 13 Jan 1999 20:43:48 -0500, sta...@West.Sun.COM (Stanley Friesen
>> [Contractor]) wrote:
>>
>> >Note, it is important, nay vital, that one use the "resource allocation
>> >is initialization" paradigm within any constructor that may throw an
>> >exception. See below for more details.
>>
>> I think that's a bit strong. I'd say it is "helpful and less
>> error-prone". Using try/catch to manage resources in constructors
>> works perfectly well also.
>
>I don't think using try/catch to manage resources in constructors, or
>anywhere else, "works perfectly well". Code that does this is harder to
>understand,

Sometimes, if the resource management is simple, it's easier to understand.
Have you ever tried to follow the rule "always use std::for_each to operate
on all elements of a sequence"? It's a similar situation. If the operation
is simple enough, separating it from the loop can be more complicated than
simply writing it out, and harder to read. The same can be said of resource
management, especially if you don't already have a component like auto_ptr
lying around to manage the resource.

>and insure works correctly by looking at the code, and gets
>out of hand very quickly:

<complicated example snipped>

Of course, I would never nest try/catch blocks that way. Mr. Blargg is
illustrating why I said it was "helpful and less error prone".

There is at least one good reason to use try/catch blocks instead of
"resource allocation is initialization": it can result in more efficient
code, especially when cleanup code in destructors really only needs to be
executed in case of an exception. The try/catch block tells the compiler
where the error-handling code is, and gives it the opportunity to move the
catch code away from the rest, improving locality of reference and
eliminating the cost of destructor calls. Compare these two functions:

void f( Bar& bar )
{
Foo* foo = new Foo;
try {
bar.adopt_a_foo( foo );
// bar owns the foo. Function can now return
} catch(...) {
delete foo; // this code still swapped out
}
}

void g( Bar& bar )
{
std::auto_ptr<Foo> foo = new Foo;
bar.adopt_a_foo( foo );
// auto_ptr destructor executes here even if no error occurs
}

Of course, the auto_ptr solution is better in almost all cases, but in
time-critical code the try/catch approach may be superior.

-Dave

Valentin Bonnard

unread,
Jan 22, 1999, 3:00:00 AM1/22/99
to
Bjarne Stroustrup wrote:
>
> Valentin Bonnard <bonn...@pratique.fr> writes:

> > It was perhaps a bit exagerated, but:
> >
> > You have written that it is a bad in idea to throw from a
> > copy ctor.
>
> I'd say! I did not "say that it's more dangerous to throw an exception
> in a ctor than anywhere else" yet you single me out among authors who
> "simply don't understand the way exceptions work" because I "have
> written that it is a bad in idea to throw from a copy ctor".
>
> > It isn't a bad idea at all; it's even a very
> > common and safe practice (copy ctors often have to allocate
> > memory and may throw bad_alloc).
> >
> > A copy ctor should never modify external objects (except
> > perhaps for mutable state), it should only change *this.
> > So if it is aborted, it leaves no external objects in an
> > undefined state.
>
> > A class with a copy ctor which throws can safely be used
> > with the STL (has the value_type for a Containner).
>
> No.

If so, it means that it isn't resonnable to use the STL with itself !

A common design test for a template class X<T> is to use it with
itself: X< X<T> >. Thus containners all fail with this test.

> I think you are mistaken.

Not really, just unclear

> For example, consider insert() into a
> vector<X>. In the cases where you don't simply overwrite existing elements.
> X's copy constructor is used to copy elements into the vector.

Yes

> If X's copy constructor throws an exception for the single elemnent
> insert() the container is guaranteed to be unchanged.

If the element is inserted at the end(), yes.

> That guarantee
> is not provided for the multiple element insert(). Some elements (but
> not all) may be inserted, maybe no elements are inserted, maybe an
> element that is not a copy of an "inserted" element is inserted.

Yes, the content of the vector isn't predictable, and with vector
usual layout, it cannot be predictable.

> Maybe the vector is in a state where it cannot be destructed because
> the copy loop was interrupted by an exception.

I don't think so. If you find anything with imply that, then
a DR should be issued.

> Similar problems can occur for some algorithms (such as sort) if
> a copy constructor throws an exception.

The value of the elements aren't predictable, but with still have
valid objects if we had valid objects before.

> For programs that are meant to be resillient against errors,
> I'm willing to program very defensively to avoid this.

Never re-using objects which ``failed'' (except with the implicit
dtor call) is fairly safe.

> (PS. I consider the limited exception safety properties guaranteed by
> the standard library reasonable and will eventually get around to
> writing something to explain why and what the alternatives were).

If they don't imply that vector is valid after a failed insert
then clearly they are unresonnable.

> > I have read in many places that it's a bad idea to throw
> > from a ctor. This feeling comes from a poor understanding
> > (or no understanding at all) of C++ exceptions,
>
> But you have not read that in my writings. Please don't lump me with
> unspecified authors with "a poor understanding (or no understanding
> at all) of C++ exceptions."

Sorry, you wrote a particular case of this statement. This particular
case may be resonnable even if the rest is non sens.

I think that it was an error to put you in the same bag as authors
you really don't understand C++ at all. I won't do it again ! (At least
if you only write things which please me I won't do it again.)

> One of the major reasons I designed exceptions was to get a clean way
> of reporting errors in constructors. As you know, I recommend exceptions
> as the way to deal with errors in constructors unless there are specific
> reasons not to.

> > (It's in C++PL 3rd, in the one the advices lists - I want to


> > add that I think that this book is very good, except for a few
> > details. I often recommand it.)
>
> Thanks.
>
> The recommendation against throwing exceptions from copy constructors
> is 14.11[9]:
>
> Avoid throwing exceptions from copy constructors; 14.4.6.1.
>
> Maybe "Avoid" could be read as "never under any circumstances". If so,
> I should rephrase it.
>
> Here is the quote from 14.4.6.1 referred to:
>
> Copy constructors (10.4.4.1) are special in that they are
> invoked implicitly and because they often both acquire and

^^^^^^^


> release resources. In particular, the standard library
> assumes proper - non-exception-throwing - behavior of copy
> constructors.

1) This can't be called 'proper' behaviour, because it means that
throwing exceptions is improper and bad style
2) Where does it says that ?

> For these reasons, care should be taken that
> a copy constructor throws an exception only in truly
> disastrous circumstances. Complete recovery from an exception
> in a copy constructor is unlikely to be feasible in every
> context of its use. To be even potentially safe, a copy
> constructor must leave behind two objects, each of which
> fulfills the invariant of its class (24.3.7.1).

If they acquire ressources, how could they avoid exceptions except
by calling abort ?

Most copy ctors will invoke new, or have make some string, or vector
ctor call. Avoiding that means:
- design every object as CoW (completly unresonnable)
- abort when you get a bad_alloc (also unresonnable)

> Naturally, copy assignment operators should be treated with
> as much care as copy constructors.

It almost as difficult to avoid exceptions with assignement op than
with copy ctors.

> I think that constructors that are called by standard containers should
> be treated with extra care compared to other constructors.
> For the same reasons, we should take extra care with copy assignments.

--

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

[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]

Andrei Alexandrescu

unread,
Jan 23, 1999, 3:00:00 AM1/23/99
to
Dave Abrahams wrote in message <78ae3p$7s1$1...@news.harvard.net>...

>void f( Bar& bar )
>{
> Foo* foo = new Foo;
> try {
> bar.adopt_a_foo( foo );
> // bar owns the foo. Function can now return
> } catch(...) {
> delete foo; // this code still swapped out
> }
>}
>
>void g( Bar& bar )
>{
> std::auto_ptr<Foo> foo = new Foo;
> bar.adopt_a_foo( foo );
> // auto_ptr destructor executes here even if no error occurs
>}
>
>Of course, the auto_ptr solution is better in almost all cases, but in
>time-critical code the try/catch approach may be superior.


C'mon, just how often did the poor inline destructor of auto_ptr (which
during unwinding costs basically only a comparison against zero) bothered
you, even in "time-critical code"?

Besides, entering a try block is usually more costly than that test. I tend
to disagree that f() is faster than g() as you wrote them above on most
implementations.

Andrei

Spencer Simpson

unread,
Jan 23, 1999, 3:00:00 AM1/23/99
to

Andrei Alexandrescu wrote:
>>
>> (snip)
>>
>> Throwing from a constructor is a very good technique, much better
>> than the pre-exceptions "construct-then-init" paradigm.

Paul Grealish replied:

>Could you provide some justification for the
>above statement.
>

>Given:
> struct A { bool init(void); };
>
>I cannot see why:
> try
> {
> A a;
> }
> catch (...)
> {
> cerr << "Error constructing an A instance" << endl;
> }
>
>is superior to:
> A a;
> if ( !a.init() )
> cerr << "Error constructing an A instance" << endl;


But it is. Think about:

void cant_cope()
{
A a;
}

void can_cope ()
{
try
{
cant_cope();
}
catch (...)
{
cerr << "can_cope() just handled an exception cant_cope() couldn't
handle."
};
}


HTH

Dave Abrahams

unread,
Jan 23, 1999, 3:00:00 AM1/23/99
to

Matt Austern wrote in message ...

>It's possible, actually, to implement vector<T> so that it always
>remains in a consistent state even if T's copy constructor throws an
>exception during multiple-element insert, where by "consistent
>state" I mean that the vector satisfies its class invariants and can
>safely be destroyed. A little bit tricky, yes, but not impossible.


In fact, it's impossible to implement vector<T> so that it *doesn't* have
this property. If your vector can't be destroyed after an exception or it
ever fails to satisfy its invariants, it isn't standard-conforming. This is
true of all of the standard containers. Those changes to the standard were
approved at the committee's London meeting, I believe. The exception-safety
guarantees approved in London were further strengthened at the Morristown
meeting in November of '97, just before the FDIS was approved, to make it
practical to build entire exception-safe _systems_ from the standard
components.

As far as I know, there is no comprehensive guide to the exception-safety
guarantees given by the standard currently in print, but the upcoming
English language edition of Nico Josuttis's book "The C++ Standard Library"
does have that information, though.

>The key is that uninitialized_copy can easily be made commit-or-
>rollback (either it succeeds or it does nothing), and that once you've
>succeeded with the uninitialized_copy all you're doing is performing
>assignments from one element of the vector to another.
>
>I believe that Dave Abrahams and I have independently written versions
>of vector<>::insert that satisfy this property. If you find that my
>implementation of vector<> doesn't satisfy it, then you will have
>found a serious bug that I'd like to know about.

And other implementors have followed us. The latest version of the
Metrowerks Standard C++ library satisfies all of the standard's
exception-safety requirements.

-Dave

Dave Abrahams

unread,
Jan 23, 1999, 3:00:00 AM1/23/99
to
Valentin Bonnard wrote in message <36A8D6...@pratique.fr>...

>Bjarne Stroustrup wrote:
>>
>> Valentin Bonnard <bonn...@pratique.fr> writes:
>> > A class with a copy ctor which throws can safely be used
>> > with the STL (has the value_type for a Containner).
>>
>> No.
>
>If so, it means that it isn't resonnable to use the STL with itself !


For a while that was the case. I think that while Valentin understands the
current state of the standard, he may suffer from some ignorance of the
history of the problem. Bjarne, on the other hand may have been too busy at
the time the relevant fixes were made to notice. I do remember quite clearly
that Bjarne argued convincingly that something had to be done about it,
because vector<string> ought to be a safe construct.

This clearly indicates an understanding of the problem, since the copy
constructor of string can throw exceptions.

>> If X's copy constructor throws an exception for the single elemnent
>> insert() the container is guaranteed to be unchanged.
>
>If the element is inserted at the end(), yes.


Actually, you're both a bit wrong, here. If you use push_back(), and an
exception occurs, there are no effects. If you use insert(), the vector
might have any valid and consistent contents, but they are unpredictable.
For example, the vector might be empty, or it might contain all of its
previous contents up to the insertion point. In no case will it cause
undefined behavior.

Of the other standard containers, only deque exhibits this kind of
unpredictability for single-element insertions.

>> That guarantee
>> is not provided for the multiple element insert(). Some elements (but
>> not all) may be inserted, maybe no elements are inserted, maybe an
>> element that is not a copy of an "inserted" element is inserted.


True, another element may be inserted, but only a valid one. For example, an
element filled with a random bit pattern could not be inserted (unless of
course the element type allowed that, e.g. int).

>Yes, the content of the vector isn't predictable, and with vector
>usual layout, it cannot be predictable.


In fact, the layout alone doesn't force this effect. If you were willing to
change some of the efficiency and iterator invalidation guarantees, it would
be possible. It is the interaction of these requirements that makes it
impossible to give stronger exception-safety for std::vector<>::insert().

>> Maybe the vector is in a state where it cannot be destructed because
>> the copy loop was interrupted by an exception.
>
>I don't think so. If you find anything with imply that, then
>a DR should be issued.


Valentin is correct as of the London committee meeting. This was not always
the case.

>> Similar problems can occur for some algorithms (such as sort) if
>> a copy constructor throws an exception.
>
>The value of the elements aren't predictable, but with still have
>valid objects if we had valid objects before.

Again this is correct since London, but was not always true.

>> For programs that are meant to be resillient against errors,
>> I'm willing to program very defensively to avoid this.
>
>Never re-using objects which ``failed'' (except with the implicit
>dtor call) is fairly safe.


That is a bit more defensive than you need to be, in general. In many cases
it is possible to build components which are completely resilient in the
face of exceptions. Indeed it would be difficult to write any program which
could recover from errors and continue to run otherwise.

>> I think that constructors that are called by standard containers should
>> be treated with extra care compared to other constructors.
>> For the same reasons, we should take extra care with copy assignments.


In light of recent developments in the standard, I think this view is
somewhat out-of-date. Probably, in light of the broad (and overwhelmingly
positive) influence of C++ PL 3rd edition, some revisions would be
appropriate.

-Dave

Siemel Naran

unread,
Jan 23, 1999, 3:00:00 AM1/23/99
to
On 22 Jan 1999 00:54:12 -0500, Bjarne Stroustrup <b...@research.att.com> wrote:

>(PS. I consider the limited exception safety properties guaranteed by
>the standard library reasonable and will eventually get around to
>writing something to explain why and what the alternatives were).

Here's one way:
Make a reference counted vector
Copy 'v' into 'vold' with "vold=v"
Now modify 'v'.
If there was an error in modifying 'v', do "v=vold".
Now 'vold' goes out of scope.


> Avoid throwing exceptions from copy constructors; 14.4.6.1.

Should also say the same for operator= and destructor.
What exceptions are reasonable to throw from a copy ctor or op=.
I can only think of std::bad_alloc.


>Maybe "Avoid" could be read as "never under any circumstances". If so,
>I should rephrase it.

No, std::bad_alloc is always possible.

--
----------------------------------
Siemel B. Naran (sbn...@uiuc.edu)
----------------------------------

[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]

Howard Hinnant

unread,
Jan 23, 1999, 3:00:00 AM1/23/99
to
In article <78as9t$fdr$1...@news.harvard.net>, "Dave Abrahams"
<abra...@motu.com> wrote:

> And other implementors have followed us. The latest version of the
> Metrowerks Standard C++ library satisfies all of the standard's
> exception-safety requirements.

I would like to add that this effort was aided by the exception safety
tests that Dave has written, and is much appreciated.

Howard Hinnant
Senior Library and Performance Engineer
Metrowerks

Dave Abrahams

unread,
Jan 23, 1999, 3:00:00 AM1/23/99
to
Andrei Alexandrescu wrote in message <36a8f...@10.1.1.65>...

>C'mon, just how often did the poor inline destructor of auto_ptr (which
>during unwinding costs basically only a comparison against zero) bothered
>you, even in "time-critical code"?


auto_ptr's destructor translates to a comparison against zero, a call to the
destructor, and a call to operator delete. Which part of that is inlined?
Oftentimes all of that code (including the comparison) will be wrapped into
a single function by the implementation, meaning that writing "delete x"
always calls a function, no matter what you do.

In fact, when I was writing the first exception-safe implementation of the
STL as a proof-of-concept for the C++ committee, it was important to
illustrate that exception-safety need not incur ANY runtime cost watsoever.
I had to satisfy those that thought of the STL as purely a device for fast
computation on ideal systems where errors could always be prevented "in
advance" (if your program crashes, buy more memory and run it again). So it
was important to me to eschew using techniques like auto_ptr for resource
management.

>Besides, entering a try block is usually more costly than that test. I tend
>to disagree that f() is faster than g() as you wrote them above on most
>implementations.

I think your information is out-of-date. Many compilers implement
zero-overhead exception handling; Metrowerks is just one that I know of. It
is well-documented that entering a try block need not incur any overhead at
all. If your compiler makes you pay for try blocks, you should complain to
your vendor!

-Dave

Nathan Myers

unread,
Jan 24, 1999, 3:00:00 AM1/24/99
to
Dave "Bruiser" Abrahams<abra...@motu.com> wrote:

>Matt "Wild Man" Austern wrote:
>>I believe that Dave Abrahams and I have independently written versions
>>of vector<>::insert that satisfy this property. If you find that my
>>implementation of vector<> doesn't satisfy it, then you will have
>>found a serious bug that I'd like to know about.
>
>And other implementors have followed us. The latest version of the
>Metrowerks Standard C++ library satisfies all of the standard's
>exception-safety requirements.

This is big news! Odd that we didn't see an announcement.
Which version of MW has the conformant exception handling?

--
Nathan Myers
n...@nospam.cantrip.org http://www.cantrip.org/

Howard Hinnant

unread,
Jan 24, 1999, 3:00:00 AM1/24/99
to
{This thread seems to move rather environment specific:
Please move further discussion to an appropriate medium,
probably e-mail. Thank you. -dk}

In article <78ebhq$79a$1...@shell7.ba.best.com>, n...@nospam.cantrip.org


(Nathan Myers) wrote:
> This is big news! Odd that we didn't see an announcement.
> Which version of MW has the conformant exception handling?

MSL 4.1. This is the Pro4 version + a public net patch. The associative
containers in this version may still create a memory leak if an exception
is thrown during their copy constructors. This bug has been fixed for
Pro5 (due out this Spring).

-Howard

Dave Abrahams

unread,
Jan 24, 1999, 3:00:00 AM1/24/99
to

Nathan Myers wrote in message <78ebhq$79a$1...@shell7.ba.best.com>...

>Dave "Bruiser" Abrahams<abra...@motu.com> wrote:
>
>This is big news! Odd that we didn't see an announcement.
>Which version of MW has the conformant exception handling?


CodeWarrior Pro 4.1, which is the stock Pro4 installation with the addition
of Metrowerks' 4.1 patches, available from their website
<http://www.metrowerks.com>

-Dave

P.S. How did I earn the nickname "Bruiser"?

Siemel Naran

unread,
Jan 25, 1999, 3:00:00 AM1/25/99
to
On 23 Jan 1999 22:21:10 -0500, Dave Abrahams <abra...@motu.com> wrote:
>Andrei Alexandrescu wrote in message <36a8f...@10.1.1.65>...

>>C'mon, just how often did the poor inline destructor of auto_ptr (which
>>during unwinding costs basically only a comparison against zero) bothered
>>you, even in "time-critical code"?

I. Why the time overhead usually does not matter

I agree with Andrei, but with an extended reason. In short, if
performance is of the essence, we should be passing by value.
This allows us to create objects on the stack and allows us to
take advantage of the absence of aliasing. Eg,

class Thing
{
private:
complex<double> c1, c2;
public:
Thing(complex<double> c1_, complex<double> c2_) : c1(c1_), c2(c2_) { }
}

int main() { complex<double> c(3.0,4.0); Thing t(c,c); }

As the copy constructor and destructor of complex<T> are inline,
and as the two arg constructor is also inline, a good compiler
should be able to make the above code maximally efficient.

In my experience creating lightweight objects on the heap seems
to be unusual. Objects we create on the heap tend to be
heavyweight objects. In this case, the cost saved by one less
pointer comparison pales in comparison with the cost of
functions (often virtual functions) working on the object. So
the time saved is negligible.

II. Even still, the compiler can eliminate the time overhead

Moreover, the compiler may automatically convert code using
std::auto_ptr to code using raw pointers along with a
try-catch block. That is, function g(...) may be converted
to function f(...). From an old post:

>void f( Bar& bar )
>{
> Foo* foo = new Foo;
> try {
> bar.adopt_a_foo( foo );
> // bar owns the foo. Function can now return
> } catch(...) {
> delete foo; // this code still swapped out
> }
>}
>
>void g( Bar& bar )
>{
> std::auto_ptr<Foo> foo = new Foo;
> bar.adopt_a_foo( foo );
> // auto_ptr destructor executes here even if no error occurs
>}

This optimization is possible if all the relevant functions of
auto_ptr are inline (most implementations indeed do this), and if
the 'adopt_a_foo' function is inline (which is certainly the case
if performance is of the essence). The nicest thing about this
optimization is that it is a general optimization and requires few
special rules. Let us suppose that the 'adopt_a_foo' function
looks like this:

// note that auto_ptr passed by value
inline void Bar::adopt_a_foo(std::auto_ptr<Foo> foo) {
if (somefunction(foo.get())) throw 1;
thefoo=foo;
}

Now let's deal with the function g() and expand out the inline
functions (note that I've added a "cout" statement, just for
fun). Pay attention to the comments "ALWAYS EXECUTE".

// original


void g( Bar& bar )
{
std::auto_ptr<Foo> foo = new Foo;

bar.adopt_a_foo( foo ); // note: calls auto_ptr's copy ctor
cout << foo.get();
}

// after inlining auto_ptr's 1-arg ctor, copy ctor, and dtor


void g( Bar& bar )
{

Foo * foo = new Foo;
Foo * tmp = foo; // making copy of 'foo' to pass to 'adopt_a_foo'
foo=0
bar.adopt_a_foo(tmp);
delete tmp; // ALWAYS EXECUTE (even if 'adopt_a_foo' throws)
cout << foo.get();
delete foo; // ALWAYS EXECUTE
}

// at the 2nd last and last lines, foo is null so we have this
// note that this step eliminates your concern:


// "auto_ptr destructor executes here even if no error occurs"

void g( Bar& bar )
{

Foo * foo = new Foo;
Foo * tmp = foo;
bar.adopt_a_foo(tmp);
delete tmp; // ALWAYS EXECUTE
cout << (Foo*)0;
}

The above step may use the special case rule that "delete null"
has no effect. Below, we'll see that if the compiler inlines
the code of "delete ptr" then no special case rule is required.


// now eliminate unnecessary variables -- ie, use 'foo' instead of 'tmp'
// in effect, we are doing this
// bar.adopt_a_foo(std::auto_ptr<Foo>(new Foo));
// maybe this is what we should have written in the first place


void g( Bar& bar )
{

Foo * tmp = new Foo;
bar.adopt_a_foo(tmp);
delete tmp; // ALWAYS EXECUTE
cout << (Foo*)0;
}

// now inline the call to adopt_a_foo


void g( Bar& bar )
{

Foo * tmp = new Foo;
if (somefunction(tmp)) throw 1;
bar.thefoo.ptr=tmp;
tmp=0;
delete tmp; // ALWAYS EXECUTE
cout << (Foo*)0;
}

// introduce a try-catch block
// our motivation is that if 'tmp' is null, then "delete tmp" has no effect
// in short, if "throw 1" never executes, then "delete tmp" need not either
// note that we've removed the comment "ALWAYS EXECUTE"


void g( Bar& bar )
{

Foo * tmp = new Foo;
try
{
if (somefunction(tmp)) throw 1;
bar.thefoo.ptr=tmp;
}
catch (int&) { delete tmp; throw; }
cout << (Foo*)0;
}

This final version is maximally efficient.

If it is known that
somefunction(Foo const *)
always returns #false, then the above simplifies to

void g( Bar& bar )
{

Foo * tmp = new Foo;
bar.thefoo.ptr=tmp;
cout << (Foo*)0;
}

Incidentally, we note that the compiler creates 'tmp' in a
register. A register is basically a small amount of RAM inside
the CPU. Therefore the above is equivalent to the terse

void g( Bar& bar )
{

bar.thefoo.ptr=new Foo;
cout << (Foo*)0;
}

>auto_ptr's destructor translates to a comparison against zero, a call to the
>destructor, and a call to operator delete. Which part of that is inlined?
>Oftentimes all of that code (including the comparison) will be wrapped into
>a single function by the implementation, meaning that writing "delete x"
>always calls a function, no matter what you do.

The destructor of auto_ptr is just this
auto_ptr<T>::~auto_ptr() { delete ptr; }
The code for "delete ptr" checks 'ptr' against null. If 'ptr'
is null, then nothing happens. Otherwise, the compiler calls
the destructor for the pointed to object and then calls either
T::operator delete or ::operator delete. We observe that
::operator delete often just calls std::free, and this function
tests 'ptr' against null again, because free(0) must have no
effect. As std::free is a big non-inline function, the second
test against null cannot be optimized away.

But I think that the first test against null should be inlined.
I don't know why your implementations don't inline it. In fact,
all three parts should be inlined, and the result after inlining
is something like this, at least for non-polymorphic T,

auto_ptr<T>::~auto_ptr() {
if (ptr!=0) { ptr->~T(); ::operator delete(ptr) }
}

But if T::~T() or T::operator delete is inline, there could be
some code bloat. So then the compiler could apply the following
criterion. If we compile for speed inline the steps of
"delete ptr"; otherwise, don't inline the steps of "delete ptr".

The advantage about "delete ptr" being inline is that the
compiler can see that "delete 0" has no effect. Hence if the
compiler knows that 'ptr' is 0, then "delete ptr;" simplifies
to ";" -- that is, to a no-operation.

>In fact, when I was writing the first exception-safe implementation of the
>STL as a proof-of-concept for the C++ committee, it was important to
>illustrate that exception-safety need not incur ANY runtime cost watsoever.
>I had to satisfy those that thought of the STL as purely a device for fast
>computation on ideal systems where errors could always be prevented "in
>advance" (if your program crashes, buy more memory and run it again). So it
>was important to me to eschew using techniques like auto_ptr for resource
>management.

Fine. How about the lightweight optimization technique described
above? The optimization makes the use of any lightweight type
(eg, complex<double>) maximally efficient, so every good compiler
should do it. Ie, it's not only good for std::auto_ptr. Eg,
consider these two fragments. First,
std::complex result=lhs+rhs; // C++ style
And second,
std::complex result; // don't initialize to (0,0)
result.real=lhs.real+rhs.real; // C style
result.imag=lhs.imag+rhs.imag;
The C style code is maximally efficient. On most compilers, the
C++ style code is less efficient. But with the full optimization
described above (inlining, etc), the C++ style is maximally
efficient too.

The nice thing about this optimization is that it allows us to
write code that is more maintainable and forget about the annoying
details of try-catch blocks, etc.


--
----------------------------------
Siemel B. Naran (sbn...@uiuc.edu)
----------------------------------

[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]

Bjarne Stroustrup

unread,
Jan 25, 1999, 3:00:00 AM1/25/99
to

"Dave Abrahams" <abra...@motu.com> writes:

> For a while that was the case. I think that while Valentin understands the
> current state of the standard, he may suffer from some ignorance of the
> history of the problem. Bjarne, on the other hand may have been too busy at
> the time the relevant fixes were made to notice. I do remember quite clearly
> that Bjarne argued convincingly that something had to be done about it,
> because vector<string> ought to be a safe construct.
>
> This clearly indicates an understanding of the problem, since the copy
> constructor of string can throw exceptions.

:-)

It may be slightly more accurate to say that I missed the implications
of a tiny (one sentence) change to the standard.


> > [bs:]


> >> That guarantee
> >> is not provided for the multiple element insert(). Some elements (but
> >> not all) may be inserted, maybe no elements are inserted, maybe an
> >> element that is not a copy of an "inserted" element is inserted.
>

> True, another element may be inserted, but only a valid one. For example, an
> element filled with a random bit pattern could not be inserted (unless of
> course the element type allowed that, e.g. int).

> >> Maybe the vector is in a state where it cannot be destructed because


> >> the copy loop was interrupted by an exception.

That was the key point. I had argued that this should be the case
(when it wasn't) and missed that the problem had been fixed.

Sometimes it is wonderful to be proven wrong! Thanks!


and now back to the original point:

> >> I think that constructors that are called by standard containers should
> >> be treated with extra care compared to other constructors.
> >> For the same reasons, we should take extra care with copy assignments.
>

> In light of recent developments in the standard, I think this view is
> somewhat out-of-date. Probably, in light of the broad (and overwhelmingly
> positive) influence of C++ PL 3rd edition, some revisions would be
> appropriate.

Copy constructors and copy assignments are still tricky in that they are
invoked deep in container and algorithm implementations. They can't lead
to indestructible containers, but they can lead to containers with elements
we didn't really want there.

Exactly how to formulate recommendations for those cases is not obvious to
me (yet), but I'll look at the "offending" three paragraphs of "The C++
Programming Language (3rd Edition)" and update them if/when I find a definite
improvement.

- Bjarne
Bjarne Stroustrup - http://www.research.att.com/~bs

Dave Abrahams

unread,
Jan 26, 1999, 3:00:00 AM1/26/99
to

Siemel Naran wrote in message ...

>I. Why the time overhead usually does not matter

>
>I agree with Andrei, but with an extended reason. In short, if
>performance is of the essence, we should be passing by value.

<code snipped>

This argument doesn't apply for several reasons:
1. Everyone agrees that the time overhead *usually* doesn't matter.
Sometimes it does.
2. The issue at stake here is resource management. Mr. Naran's code is not
an example of resource management. It's hard to manage any resources with
built-in types like double or char* unless you use try/catch, or wrap them
in classes like std::auto_ptr.
3. In a generic container, you can't predict whether the contained type will
be lightweight or heavyweight. If maximally efficiency is important, you
have to pick a single strategy.

>In my experience creating lightweight objects on the heap seems
>to be unusual. Objects we create on the heap tend to be
>heavyweight objects. In this case, the cost saved by one less
>pointer comparison pales in comparison with the cost of
>functions (often virtual functions) working on the object. So
>the time saved is negligible.


Mr. Naran's view of the issue is somewhat narrow, I think. Not all resources
come in the form of heap-allocated objects; they can't all be managed by
auto_ptr. Two extreme examples might be files on disk and multithreading
locks. These two resources usually have use (and release) costs at opposite
ends of the spectrum.

Here's an example of resource management which would pay a really
significant penalty without try/catch: try implementing the standard
function uninitialized_fill() without a try/catch block (yes, it is
possible). Look at the generated code and compare it to the version that
uses try/catch.

>II. Even still, the compiler can eliminate the time overhead


The short version of your argument is that the "as-if" rule allows the
compiler to transform a version using auto_ptr into a version using
try/catch [Mr. Naran, I'd like to take this opportunity to encourage you to
make your posts more succinct. I usually skip over them and probably miss an
occasional gem because they are so long].

It is true that such a transformation is legal, but also quite specialized
and not broadly effective. I'm not sure you will find a single compiler that
does it. In the general case of releasing resources using other means than
operator delete it is not always possible to make the analogous
transformation.

In the case of auto_ptr, it is seldom appropriate to write an inline
destructor for a class which is going to be used primarily on the heap,
because the cost of destruction is typically swamped by the cost of memory
management. .

It is not always possible or desirable to inline everything. If you're
working with a library which manages a resource with simple function calls,
the use of an object to manage instances of that resource in an inner loop
can double or triple the cost of a critical path.

>>auto_ptr's destructor translates to a comparison against zero, a call to
the
>>destructor, and a call to operator delete. Which part of that is inlined?
>>Oftentimes all of that code (including the comparison) will be wrapped
into
>>a single function by the implementation, meaning that writing "delete x"
>>always calls a function, no matter what you do.
>
>The destructor of auto_ptr is just this
> auto_ptr<T>::~auto_ptr() { delete ptr; }
>The code for "delete ptr" checks 'ptr' against null. If 'ptr'
>is null, then nothing happens. Otherwise, the compiler calls
>the destructor for the pointed to object and then calls either
>T::operator delete or ::operator delete.

You're already in an out-of-line function at this point, since the user is
allowed to replace the library-supplied ::operator delete.

>But I think that the first test against null should be inlined.
>I don't know why your implementations don't inline it.

They may well do that. I usually don't make heap-allocated objects with
inline destructors, so the compiler can't tell what side-effects the
destructor might have, which prevents your optimization.

>How about the lightweight optimization technique described
>above?

You haven't described an optimization technique that any compiler writer can
use. You've described the as-if rule, and given some reasoning to show that
it is applicable in the case of auto_ptr. Computers aren't able to get from
here to there as easily as humans. Optimization techniques need to be
described at a much lower level.

>The optimization makes the use of any lightweight type
>(eg, complex<double>) maximally efficient, so every good compiler
>should do it. Ie, it's not only good for std::auto_ptr. Eg,

<example of complex snipped>

What does this have to do with resource management?

Andrei Alexandrescu

unread,
Jan 27, 1999, 3:00:00 AM1/27/99
to

Dave Abrahams wrote in message <78iata$raj$1...@news.harvard.net>...

>In the case of auto_ptr, it is seldom appropriate to write an inline
>destructor for a class which is going to be used primarily on the heap,
>because the cost of destruction is typically swamped by the cost of
memory
>management. .
>
>It is not always possible or desirable to inline everything. If you're
>working with a library which manages a resource with simple function
calls,
>the use of an object to manage instances of that resource in an inner
loop
>can double or triple the cost of a critical path.


Dave,

First, it's ironic you advise Siemel not to post that long messages...
in
the middle of a lenghty article! :o)

I don't get your point here, I guess it's my problem. Could you
enlighten
me?

The discussion started as we were discussing using auto_ptr versus
try/catch/clean up by hand. I said the main difference in cost is a
comparison against zero, because

Then I got lost in your answer. Obviously, the cost of heap management
is
the most part, but this is incurred in both cases, so what you wrote
seemed
to me like a tautology. What I meant was that if really mean efficiency
and
you wrote auto_ptr's destructor inline like this:

template <typename T>
auto_ptr::~auto_ptr()
{
if (p) delete p;
}

you certainly pay only one inlined comparison for the null pointer
(compared
to the do-it-yourself case), even though the user redefined operator
delete
and made it out-of-line. By the way, is the user-defined operator delete
calld for null pointers?

Please make things clearer to me:

1. What's the penalty of auto_ptr that you see and in what cases (normal
or
while throwing an exception)?
2. Can that penalty be overcome? If yes, why even bother with the more
complicated code that does it by hand?
3. What are other cases when manual resource management is much better
that
the automated one?

Andrei

Dave Abrahams

unread,
Jan 30, 1999, 3:00:00 AM1/30/99
to

Andrei Alexandrescu wrote in message <36add...@10.1.1.65>...

>First, it's ironic you advise Siemel not to post that long messages...
>in the middle of a lenghty article! :o)


You're right that a shorter post would have made my point better, but I was
asking for succinctness, not neccessarily shortness. A point that can be
made in a single sentence is a waste of time when expressed in 5 pages.

>I don't get your point here, I guess it's my problem. Could you
>enlighten
>me?
>
>The discussion started as we were discussing using auto_ptr versus
>try/catch/clean up by hand.

I think this is why you are confused. The discussion actually started
because someone said that it was absolutely critical to always use "resource
acquisition is initialization" to manage resources. I just wanted to make
the point that there are times when try/catch was more appropriate.

>Please make things clearer to me:
>
>1. What's the penalty of auto_ptr that you see and in what cases (normal
>or while throwing an exception)?


Most compilers will not optimize the auto_ptr into a register or eliminate
the comparison against zero or move the auto_ptr's destructor out of the
main flow of control to improve locality of reference.

>2. Can that penalty be overcome? If yes, why even bother with the more
>complicated code that does it by hand?

I'm not sure what you mean by "overcome". It can be overwhelmed by the cost
of memory management, but if the type parameter has a custom operator
delete, perhaps it won't be. Those penalties, could, in theory, be overcome
by an optimizer that was smart enough, but usually they will not.

>3. What are other cases when manual resource management is much better
>that
>the automated one?

Try to write "std::uninitialized_fill", as I suggested in a previous post.

-Dave

0 new messages