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

Are throwing default constructors bad style, and if so, why?

154 views
Skip to first unread message

Andrei Alexandrescu

unread,
Sep 17, 2008, 7:28:54 AM9/17/08
to
I have the intuition that defining a type with a throwing default
constructor has bad style all over it. However, I can't seem to pinpoint
a few strong reasons to back up my belief. So I wanted to discuss it
here - do you think defining a throwing default constructor is a bad
idea, and if so, why? If not, what would be some good examples?

A default constructor can be argument-less or with all-defaulted arguments:

struct S1 { S1() { ... } };
struct S2 { S2(int x = 42) { ... } };


Andrei


--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]

wasti...@gmx.net

unread,
Sep 17, 2008, 3:58:41 PM9/17/08
to
On 17 Sep., 13:28, Andrei Alexandrescu <SeeWebsiteForEm...@erdani.org>
wrote:

> I have the intuition that defining a type with a throwing default
> constructor has bad style all over it.

Absolutely not. If, like me, you're not a fan of two-stage
construction, constructors must throw whenever they cannot fully
initialize the object, arguments or not. If they acquire any resource
that can fail - memory, a mutex, ... - the constructor can throw.

Take, for example, a C++ wrapper around an OS mutex. When you create
it, there's typically no reason to pass any arguments. Maybe some
configuration, but it will have defaults. If the class then fails to
acquire an OS mutex, what should it do? Sit there in an unusable
state, waiting for someone to call lock() just so it can throw? Or
should it throw immediately?

Mathias Gaunard

unread,
Sep 17, 2008, 4:02:25 PM9/17/08
to
On 17 sep, 13:28, Andrei Alexandrescu <SeeWebsiteForEm...@erdani.org>
wrote:

> I have the intuition that defining a type with a throwing default
> constructor has bad style all over it. However, I can't seem to pinpoint
> a few strong reasons to back up my belief. So I wanted to discuss it
> here - do you think defining a throwing default constructor is a bad
> idea, and if so, why? If not, what would be some good examples?

A likely case when you have a constructor that doesn't throw is
because your object doesn't need to own any resource but its own
memory.
Anoter likely case is because it doesn't acquire any resource in the
constructor; it sets the object to an empty state, for example (empty
std::function, null smart pointer, ...).

I personally think that empty states, which are more and more common,
are an unsafe way to program (and also add overhead). So my opinion is
that there should not be a non-throwing default constructor that
doesn't fully initialize the object and acquire all the resources it
needs. That often means no default constructor.

Alex

unread,
Sep 17, 2008, 6:28:59 PM9/17/08
to
On Sep 17, 7:28 am, Andrei Alexandrescu

<SeeWebsiteForEm...@erdani.org> wrote:
> I have the intuition that defining a type with a throwing default
> constructor has bad style all over it. However, I can't seem to pinpoint
> a few strong reasons to back up my belief. So I wanted to discuss it
> here - do you think defining a throwing default constructor is a bad
> idea, and if so, why?

Can be a bad idea because it typically indicates latent coupling which
is worse than ordinary, obvious coupling.
If default constructor throws, something has gone awry in the object
without user passing any information to it. This means that the object
is either (a) internally ill-conceived or (b) depends on something
external which is not immediately obvious.

Coupling is sometimes unavoidable and in those cases, it should be as
obvious as possible - best indicated at compile time.
For example, there's no use for a socket without it's peer on the
other side - coupling is an intrinsic part of the functionality, hence
unavoidable.
According to RAII principle, connect should be done at create time,
which means user should be forced to provide the IP:port to the
constructor and the default constructor should be private.

> If not, what would be some good examples?

Let's say that an object uses temporary file or allocates memory from
heap. For user's convenience, we provide RAII creation/deletion of the
file/memory. Default constructor throws if temporary file can not be
created or memory allocated. The behavior, of course, should be
properly documented, but in this case, IMO, convenience trumps the
perils of non-obvious coupling.

The bottom line would be:

If class is not coupled with any resource external to it, throwing in
default constructor is a design or logic flaw.
If class is coupled with a named resource (file, socket,
database, ...), there should be no default constructor (and,
consequently, no throwing from default constructor).
If class is coupled with a generic resource (temporary file, heap
memory, ...) that can be acquired without user supplying any
information about it, then default constructor should throw.

My $.002

Alex

Martin Bonner

unread,
Sep 17, 2008, 6:28:55 PM9/17/08
to
On Sep 17, 12:28 pm, Andrei Alexandrescu

<SeeWebsiteForEm...@erdani.org> wrote:
> I have the intuition that defining a type with a throwing default
> constructor has bad style all over it. However, I can't seem to pinpoint
> a few strong reasons to back up my belief. So I wanted to discuss it
> here - do you think defining a throwing default constructor is a bad
> idea, and if so, why? If not, what would be some good examples?
>

Consider an object that represents a an off-screen image buffer. If
you run out of memory on the graphics card, you have to signal the
failure to create the object. The best way to do that is via an
exception. The constructor might well have default arguments for
extent and depth of the buffer. There's no obvious need for a name or
similar. I can't see the problem.

analizer

unread,
Sep 17, 2008, 6:28:59 PM9/17/08
to
On Sep 17, 3:28 pm, Andrei Alexandrescu

<SeeWebsiteForEm...@erdani.org> wrote:
> I have the intuition that defining a type with a throwing default
> constructor has bad style all over it. However, I can't seem to pinpoint
> a few strong reasons to back up my belief. So I wanted to discuss it
> here - do you think defining a throwing default constructor is a bad
> idea, and if so, why? If not, what would be some good examples?


IMHO, throwing c'tor is a good practice if this c'tor performs any non-
trivial initializations (like socket connect, file read, attach to
pipe or message queue created by other process, etc.), this is only
way to report an error during object construction. You can say that
class should provide something like initialize() function for such
operations that will return result code for operation, but this
solution have following disadvantages:
1. Non-transparent dependency: you'll need to call this function each
time you creates such objects, of course you can provide factory that
will return pointer to new object or NULL if initialize() function
have failed, but Occam's Razor learns us that this solution is bad.
2. You won't be able to declare members filled with initialize() as
const's. For example, it is much better when you able to initialize
const socket in the members initialization list in c'tor.

Main reason to use throwing c'tors is ability to graceful handling of
static variables initializations (for example, for singletons), if
exception have been thrown in c'tor then static variable won't be
marked as initialized and initialization will be repeated during the
next function call. This way you can easily handle singletons
initialization failures (I can imagine situation when we should check
some connection singleton initialization when client connection
singleton have been created before server one, and we should wait a
bit).

So, I see no problems in throwing c'tors usage. I agree, exceptions
are slow, but if c'tor have failed this means something really bad
happened and performance is not a high priority here.

Dmitry

Nevin :-] Liber

unread,
Sep 17, 2008, 6:28:58 PM9/17/08
to
In article <K7BHn...@beaver.cs.washington.edu>,
Andrei Alexandrescu <SeeWebsit...@erdani.org> wrote:

> I have the intuition that defining a type with a throwing default
> constructor has bad style all over it. However, I can't seem to pinpoint
> a few strong reasons to back up my belief. So I wanted to discuss it
> here - do you think defining a throwing default constructor is a bad
> idea, and if so, why? If not, what would be some good examples?

I think it is bad mainly because it goes against people's expectations.

I know it certainly went against my expectations when I found out the
default constructor for std::deque (under gcc 3.3.3; I haven't looked to
see if that has changed in later STL implementations) can throw.

If the default constructor of an object can throw, I suddenly have to
"be careful" when using that object as a member in another object, since
the "default constructor can throw" has a ripple effect.

Using std::deque for an example, if I have

struct Something
{
//...
std::deque<int> di;
};

In order to make the default constructor of Something non-throwing, I'd
have to write it as:

struct Something
{
Something() {}

Something(Something const& that)
: pdi(that.pdi ? new std::deque<int>(*pdi) : 0)
{}

//...

boost::scoped_ptr< std::deque<int> > pdi;
};

and the associated complexity of pointer semantics (an extra heap
allocation, test for NULL pointer on access, custom copy constructor,
etc.) instead of the object directly.

--
Nevin ":-)" Liber <mailto:ne...@eviloverlord.com> 773 961-1620

Alberto Ganesh Barbati

unread,
Sep 17, 2008, 6:28:55 PM9/17/08
to
Andrei Alexandrescu ha scritto:

> I have the intuition that defining a type with a throwing default
> constructor has bad style all over it. However, I can't seem to pinpoint
> a few strong reasons to back up my belief. So I wanted to discuss it
> here - do you think defining a throwing default constructor is a bad
> idea, and if so, why? If not, what would be some good examples?

Good question. Given that the constructor has no parameters to validate,
the decision to throw or not must depend on some external state.
Accessing external state in the form of global variables may be frowned
upon regardless of the issue at hand, but there is one external state
that can't be easily avoided: memory. If the default constructed object
needs to allocate any amount of memory and there is not enough, then
it's correct, IMHO, for the constructor to throw.

Just my opinion,

Ganesh

Marsh Ray

unread,
Sep 17, 2008, 6:29:00 PM9/17/08
to
On Sep 17, 6:28 am, Andrei Alexandrescu

<SeeWebsiteForEm...@erdani.org> wrote:
> do you think defining a throwing default constructor is a bad
> idea, and if so, why?

Well, if any constructor (default or otherwise) can't complete its
work properly, it has to throw, right?

Since the default ctor generally isn't taking any args in the usual
sense, it's either going to:
1. never throw
2. always throw, or
3. throw or not based on "ambient" state or other runtime events

Cases 1 and 2 seem to be uninteresting for our discussion.

For case 3, I can think of some sources of ambient state that could
affect successful construction:

1. global data
I suspect the general problems with global data may be the root of
your intuition.

2. file-scope static data
Similar to global data, but IMHO not really as severe.

Some run-time events:

1. Resource exhaustion
Surely it's reasonable for a default constructor occasionally to call
'new'?

2. An exception thrown from or failure code returned from code that's
not under your control, maybe not even written in C++.
In my work, I'm constantly writing classes to manage OS-handle-based
objects and and other API calls that expect to be called in pairs. For
example, creating a Win32 critical section object generally doesn't
require any parameters to the constructor, but has been known to fail
at runtime (perhaps this is just another example of resource
exhaustion though?)

Perhaps a better example: Before I can use sockets under Win32, I have
to call a 'WSAStartup()' function, which is supposed to be paired with
an eventual 'WSACleanup()' call. My habit is to use a ctor/dtor for
this kind of thing. The startup call (historically anyway) will
succeed or fail based on a comaprison of the level of functionality I
request (which is generally compile-time constant) vs. patches and
updates to the end-user's OS. My constructor for this class needs no
arguments, but it's success cannot be guaranteed in practice.

So I think finding you've coded a throwing default ctor might be a
good signal to double-check the design, but it's also something that's
going to arise naturally in real-world programs utilizing RAII to the
fullest, most general sense.

But highly-general platform-neutral library code that doesn't have any
dependencies outside of the current standard (i.e., the kind of stuff
you like to write :-), I can't think of many cases (new/bad_alloc)
where it would makes much sense.

- Marsh

David Abrahams

unread,
Sep 17, 2008, 6:28:58 PM9/17/08
to
On Sep 17, 4:28 am, Andrei Alexandrescu

<SeeWebsiteForEm...@erdani.org> wrote:
> I have the intuition that defining a type with a throwing default
> constructor has bad style all over it. However, I can't seem to pinpoint
> a few strong reasons to back up my belief. So I wanted to discuss it
> here - do you think defining a throwing default constructor is a bad
> idea, and if so, why?

I don't know why this idea that any constructor shouldn't throw keeps
raising its ugly head. I keep killing it off, and it keeps coming
back. Constructors and exceptions have a beautiful relationship, as
throwing constructors allow the establishment of strong class
invariants. There's certainly nothing inherently different between

X();

and

X(5);

There is only one angle I can think of that could lead to a plausible
argument against throwing default ctors. Most movable types need to
have a resourceless "moved-from" state in their invariants. Resource
allocation being the primary source of exceptions, one can usually
eliminate throws from the default ctor by having it produce the "moved-
from" state without technically weakening the invariant. Since
nonthrowing operations can be used in more liberally than throwing
ones, such a design theoretically improves usability.

However, I don't buy it. The lifetime of "moved-from" objects is
sufficiently short (usually just a prelude to destruction) that
limiting the resourceless state to objects that have actually been
moved-from and not allowing people to easily construct such objects
can still be useful in increasing clarity, maintainability, and
security. Remember, class invariants aren't 100% bulletproof anyway,
as they can be violated during construction and destruction (not to
mention mutation). Being able to say that "except for moved-from
objects, I always know that I own my resources" is very powerful.

--
Dave Abrahams
Boostpro Computing
http://boostpro.com

blargg

unread,
Sep 18, 2008, 4:23:26 PM9/18/08
to
In article
<ec1fa5b1-dac7-4111...@a1g2000hsb.googlegroups.com>,
wasti...@gmx.net wrote:

> On 17 Sep., 13:28, Andrei Alexandrescu <SeeWebsiteForEm...@erdani.org>
> wrote:
> > I have the intuition that defining a type with a throwing default
> > constructor has bad style all over it.
>
> Absolutely not. If, like me, you're not a fan of two-stage
> construction, constructors must throw whenever they cannot fully
> initialize the object, arguments or not. If they acquire any resource
> that can fail - memory, a mutex, ... - the constructor can throw.
>
> Take, for example, a C++ wrapper around an OS mutex. When you create
> it, there's typically no reason to pass any arguments. Maybe some
> configuration, but it will have defaults. If the class then fails to
> acquire an OS mutex, what should it do? Sit there in an unusable
> state, waiting for someone to call lock() just so it can throw? Or
> should it throw immediately?

Or a similar variant: wait to acquire the mutex until the first member
function call that uses it, causing the exception as before. Allocating
resources in the constructor allows member functions to rely on them
being already allocated, rather than checking each and allocating if not
already allocated. It also allows the user to handle resource allocation
exceptions ahead of time, eliminating the possibility of those
exceptions from member function calls. In many cases, that can allow
member functions to throw no exceptions, simplifying user code.

If the user really wants to delay acquisition, perhaps until first use,
he should delay constructing the object. That's something to be
implemented once in a class template, or just implemented via a smart
pointer and new. The class template could even automatically create the
object the first time -> is used on it. This approach doesn't burden
those users who don't need delayed acquisition (construction).

Dave Harris

unread,
Sep 18, 2008, 4:22:49 PM9/18/08
to
> I have the intuition that defining a type with a throwing default
> constructor has bad style all over it. However, I can't seem to
> pinpoint
> a few strong reasons to back up my belief. So I wanted to discuss it
> here - do you think defining a throwing default constructor is a bad
> idea, and if so, why? If not, what would be some good examples?

It seems fine to me, to the point that I wonder if I've misunderstood the
question. At the risk of stating the obvious, it's good to throw if it
can't allocate a resource it needs to satisfy its class invariant.

struct FixedBuffer {
size sz;
char *pData;
FixedBuffer( int sz=256 ) : sz( sz ), pData( new char[sz] ) {
}
//...
};

I don't see anything wrong with a constructor like this. You can't return
error status from a constructor, and we shouldn't expect clients to
remember to use two-phase construction or to check a status flag after
construction. This is what RAII is all about - making a strong class
invariant or throwing if you can't.

If you mean a constructor that /always/ throws, then it's bad style in
that it's a bit pointless calling it. A class like:

struct FixedBuffer {
size sz;
char *pData;
FixedBuffer( int sz ) : sz( sz ), pData( new char[sz] ) {
}
FixedBuffer() {
throw "You forgot to pass the size!";
}
//...
};

would be better off making the default constructor private so the mistake
is detected at compile-time.

-- Dave Harris, Nottingham, UK.

Jonathan Jones

unread,
Sep 18, 2008, 4:22:32 PM9/18/08
to

> On 17 Sep., 13:28, Andrei Alexandrescu <SeeWebsiteForEm...@erdani.org>
> wrote:
> > I have the intuition that defining a type with a throwing default
> > constructor has bad style all over it.
>
> Absolutely not. If, like me, you're not a fan of two-stage
> construction, constructors must throw whenever they cannot fully
> initialize the object, arguments or not. If they acquire any resource
> that can fail - memory, a mutex, ... - the constructor can throw.
>
> Take, for example, a C++ wrapper around an OS mutex. When you create
> it, there's typically no reason to pass any arguments. Maybe some
> configuration, but it will have defaults. If the class then fails to
> acquire an OS mutex, what should it do? Sit there in an unusable
> state, waiting for someone to call lock() just so it can throw? Or
> should it throw immediately?

What would the default constructor _usefully_ do that might lead to an
error requiring a throw? In your example, you could easily refactor
the code to only manipulate the OS mutex through a non-default
constructor, leaving the default constructor to perform simple, safe
initialization. In my opinion, this seems like better style, but
perhaps I'm overlooking something.

Andre Kaufmann

unread,
Sep 18, 2008, 5:28:53 PM9/18/08
to
analizer wrote:
> On Sep 17, 3:28 pm, Andrei Alexandrescu
> <SeeWebsiteForEm...@erdani.org> wrote:
>
> So, I see no problems in throwing c'tors usage. I agree, exceptions

I think there are perhaps some minor problems:

a) A global object, which throws, will cause the application to
teminate / crash, without being able to handle the error.

b) If one uses function pairs to be called in the
constructor / destructor and the constructor throws,
the destructor won't be called.

If RAII objects are used to handle this there shouldn't be a problem,
but the developer has to be always aware of the
destructor not being called - it's simply another pitfall.

c) How many developers expect constructors to throw ?

I tend to initialize and use resources as late as possible.
Therefore I don't have commonly the need to allocate resources and
handle errors in the constructor anyways.

> [...]

Joshua...@gmail.com

unread,
Sep 20, 2008, 2:09:46 AM9/20/08
to
On Sep 18, 1:22 pm, Jonathan Jones <hid...@this.is.invalid> wrote:
> In article
> <ec1fa5b1-dac7-4111-adb2-c847c8af8...@a1g2000hsb.googlegroups.com>,

>
>
>
> wasti.r...@gmx.net wrote:
> > On 17 Sep., 13:28, Andrei Alexandrescu <SeeWebsiteForEm...@erdani.org>
> > wrote:
> > > I have the intuition that defining a type with a throwing default
> > > constructor has bad style all over it.
>
> > Absolutely not. If, like me, you're not a fan of two-stage
> > construction, constructors must throw whenever they cannot fully
> > initialize the object, arguments or not. If they acquire any resource
> > that can fail - memory, a mutex, ... - the constructor can throw.
>
> > Take, for example, a C++ wrapper around an OS mutex. When you create
> > it, there's typically no reason to pass any arguments. Maybe some
> > configuration, but it will have defaults. If the class then fails to
> > acquire an OS mutex, what should it do? Sit there in an unusable
> > state, waiting for someone to call lock() just so it can throw? Or
> > should it throw immediately?
>
> What would the default constructor _usefully_ do that might lead to an
> error requiring a throw? In your example, you could easily refactor
> the code to only manipulate the OS mutex through a non-default
> constructor, leaving the default constructor to perform simple, safe
> initialization. In my opinion, this seems like better style, but
> perhaps I'm overlooking something.

Why? What do you gain by having a "zombie" / uninitialized state?

Basically, it's the argument of exception-using code vs C-style code.
I like using exceptions for when I cannot acquire a resource.

You no longer have to construct the object then initialize it. Thus
you no longer have concerns about zombie objects. you also have much
less error handling code (you don't have the scattered "If failed to
initialize, return error"). This leads to less code, easier to read
code, and easier to verify correctness of code.

You will also have faster code. The extra function calls to initialize
the object and the extra if statements to return early on a failure
will perform slower than exceptions. The extra if statements will
raise instruction cache misses by adding instructions with the common
case code, whereas any decent compiler will put the exception handlers
in a far far away place in memory, increasing instruction locality for
the normal successful path.

Mathias Gaunard

unread,
Sep 20, 2008, 2:15:06 AM9/20/08
to
On 18 sep, 23:28, Andre Kaufmann <akfmn...@t-online.de> wrote:

> c) How many developers expect constructors to throw ?

Any one that ever heard about RAII, exceptions, STL or modern C++?
(pick one)


> I tend to initialize and use resources as late as possible.
> Therefore I don't have commonly the need to allocate resources and
> handle errors in the constructor anyways.

Then you're really missing the whole point of constructors, I guess.
They're made to initialize. If you want delayed initialization, just
delay construction.
Also, you're paying an extra overhead, since your object can always be
in a "non-initialized-yet" state, which means your invariant really
isn't what it ought to be.

Erik Wikström

unread,
Sep 20, 2008, 2:12:21 AM9/20/08
to

The problem with the constructor to perform a safe initialisation is
that it will leave the object in a constructed but unusable state, which
means that each call to a member function might potentially throw, or at
the very least they have to check for the unusable state when using the
object.

Another point to consider is the case when it simply does not make sense
to pass any parameters.

--
Erik Wikström

Maciej Sobczak

unread,
Sep 20, 2008, 2:10:27 AM9/20/08
to
On 18 Wrz, 22:23, blargg <blargg....@gishpuppy.com> wrote:

> Or a similar variant: wait to acquire the mutex until the first member
> function call that uses it

How would you do that in a thread-safe way?

Note: the thread-safety in this context means that when two threads
attempt to call such operation at the same time, only one should
initialize the mutex, but both should use it. I have never seen it
done properly and I have seen serious bugs in Very Important Programs
exactly because of this.

Coming back to the original subject, there are only two alternatives:
- use constructors and allow them to throw, or
- don't use constructors at all and rely on separate init functions
where there is a choice on how to report errors (by exceptions or by
error codes)

I don't claim that any of these two is better than the other because
there are justified use cases for both, but using constructors and
disallowing them to throw always leads to broken designs on a higher
level.

--
Maciej Sobczak * www.msobczak.com * www.inspirel.com

The C++ Database Access Library: soci.sourceforge.net

blargg

unread,
Sep 20, 2008, 2:12:46 AM9/20/08
to
In article <gatuku$bo2$00$1...@news.t-online.com>,

Andre Kaufmann <akfm...@t-online.de> wrote:
> analizer wrote:
> > On Sep 17, 3:28 pm, Andrei Alexandrescu
> > <SeeWebsiteForEm...@erdani.org> wrote:
> >
> > So, I see no problems in throwing c'tors usage. I agree, exceptions
>
> I think there are perhaps some minor problems:
>
> a) A global object, which throws, will cause the application to
> teminate / crash, without being able to handle the error.

Then make the global object a smart pointer and don't allocate the
object until it's first used. I consider global non-trivial objects bad
style, because of this issue.

> b) If one uses function pairs to be called in the
> constructor / destructor and the constructor throws,
> the destructor won't be called.
>
> If RAII objects are used to handle this there shouldn't be a problem,
> but the developer has to be always aware of the
> destructor not being called - it's simply another pitfall.

But one has this problem regardless of whether the default constructor
of any other classes throws, unless you're arguing that NO constructors
should throw.

> c) How many developers expect constructors to throw ?

Shouldn't a developer generally expect everything to throw, and only
write code that can't handle exceptions in the cases where none are
thrown and handling them is too costly/difficult? If things are written
correctly, in most cases handling exceptions doesn't impose much of a
burden.

> I tend to initialize and use resources as late as possible.
> Therefore I don't have commonly the need to allocate resources and
> handle errors in the constructor anyways.

You're not getting the full benefit of constructors. They are there to
establish an object's invariant. The more stringent the invariant, the
fewer possibilities member functions have to deal with.

analizer

unread,
Sep 20, 2008, 2:28:07 AM9/20/08
to
On Sep 19, 1:28 am, Andre Kaufmann <akfmn...@t-online.de> wrote:
> a) A global object, which throws, will cause the application to
> teminate / crash, without being able to handle the error.

What about to use singletons instead of "global objects"? Global
objects cause too many pitfalls such as initialization and linkage
orders etc. For singletons you can handle exceptions in c'tors
gracefully. Also this will allow to delay object initialization, it
will be initialized when it will be used first time.

>
> b) If one uses function pairs to be called in the
> constructor / destructor and the constructor throws,
> the destructor won't be called.
>
> If RAII objects are used to handle this there shouldn't be a problem,
> but the developer has to be always aware of the
> destructor not being called - it's simply another pitfall.

True. The only cure here is to use objects that will perform self
uninitialization or use guard objects those will perform
uninitialization of objects those have no d'tors.

> c) How many developers expect constructors to throw ?

All (ideally). If class method (including c'tor) throws exception,
then this should be reflected in code comments in header file. Anyone
who uses the class method should learn how this method works, for
example by reading comments to this method.

Andrei Alexandrescu

unread,
Sep 20, 2008, 2:23:01 AM9/20/08
to
Thanks to all who responded to my inquiry. There's been a great deal of
insightful answers and examples given, such as:

1. Mutex initialization. It turns out that only initialization (not
acquisition!) of a mutex could fail. For example pthread_mutex_init
could fail (see
http://opengroup.org/onlinepubs/007908775/xsh/pthread_mutex_init.html).
Interestingly, InitializeCriticalSection returns void (see
http://msdn.microsoft.com/en-us/library/ms683472(VS.85).aspx) but may
fail by throwing a structured exception on pre-Vista Windae.

2. An image buffer would require dynamic allocation in its constructor
for proper functioning, which might fail.

3. More generally, objects of which state depends on globals and
singletons might as well throw an exception in their default constructor.

Along the way, considerations regarding "moved-from" states, two-stage
construction (blech), and lazy initialization were made. Neat.

I asked the question because I'm coming from a slightly unusual angle.
I've been using a garbage-collected environment for a while. When a GC
is in the equation *together* with deterministic destruction,
Interesting Things(TM) happen. One of them is that it's a great idea to
separate teardown (destruction) from deallocation. For example:

Foo * p = new Foo(...);
...
delete p;

In a GC environment, it makes a lot of sense if the delete statement
only invokes Foo's destructor against *p and then... leaves the object
alone in a "valid but non-resource-consuming" state.

Such an approach is not foreign to many on this group; there's been many
a discussion about what was descriptively called "zombie" object states.

The advantage of separating teardown from deallocation is that there are
no more dangling pointers, and consequently even incorrect programs
could achieve predictable, reproducible, and consequently debuggable
behavior. That way you get to use GC for what it's best at (recycling
memory) and deterministic teardown for what it's best at (timely release
of resources). In fact I'd go as far as saying that I think separating
teardown from deallocation an essential ingredient in successfully
marrying deterministic teardown with garbage collection. Other schemes
could be conceived, and I've thought of a few, but at least those I
could imagine lead to a mésalliance at most.

Once we accept the reality of "destroyed but not deallocated" objects,
we need to define that state appropriately. That state claims no more
resources than the bits of Foo alone, and must be detected by all member
functions of the object such that they have defined behavior for that state.

A genuinely distinct state could be defined. A reasonable decision is to
define that state as the default constructed state. Such a decision may
not always be perfect, but it has some advantages:

a) For many types, the default constructor does construct an impersonal,
empty, aloof object that does no more than compare equal to all other
default-constructed objects. Save for collecting information from the
environment, a default-constructed object does not have any
parameterization to make it "different".

b) All member functions need to take care of the "zombie" state anyway,
and often they also need to take care of the default-constructed state,
so merging the two can simplify things.

c) The rule is easy to explain and remember. Introducing a new zombie
state defined on a per-case basis only further adds to the complications
of defining types.

d) Non-throwing default constructors occasionally facilitate writing
algorithms. For example, in Hoare's partition it's convenient to store
the pivot in a local temporary. The temporary is default-constructed and
then only swapped around. In other words, non-throwing default
constructors allow writing "conservative" algorithms on collections that
do not create any new value, but do use temporary memory to shuffle
values around.

There are disadvantages too, for example it becomes impossible to
distinguish a legit default-constructed object from one that has had a
life and kicked the bucket. For example in the mutex case it may make
sense to define a default-constructed mutex one on which initialization
did succeed, and a zombie one a mutex that is obliterated with an
illegal bit pattern. (A more portable solution would need to add a bool
to the object state.)

I'd be interesting in hearing further opinions on how garbage collection
interacts with defining object states.


Andrei

Gerhard Menzl

unread,
Sep 20, 2008, 2:28:40 AM9/20/08
to
Nevin :-] Liber wrote on throwing default constructors:

> I think it is bad mainly because it goes against people's
> expectations.

Against which people's expectations? An experienced C++ programmer
should expect any non-trivial operation to throw unless it is explicitly
documented that it doesn't.

> If the default constructor of an object can throw, I suddenly have to
> "be careful" when using that object as a member in another object,
> since the "default constructor can throw" has a ripple effect.

This is the very nature of exceptions. Except for low-level, C-style
code, all code needs to be exception safe. In modern C++, the
possibility of an exception being thrown is the norm, and its absence
is, well, the exception.

> Using std::deque for an example, if I have
>
> struct Something
> {
> //...
> std::deque<int> di;
> };
>
> In order to make the default constructor of Something non-throwing,
> I'd have to write it as:
>
> struct Something
> {
> Something() {}
>
> Something(Something const& that)
> : pdi(that.pdi ? new std::deque<int>(*pdi) : 0)
> {}
>
> //...
>
> boost::scoped_ptr< std::deque<int> > pdi;
> };
>
> and the associated complexity of pointer semantics (an extra heap
> allocation, test for NULL pointer on access, custom copy constructor,
> etc.) instead of the object directly.

What's the advantage of a non-throwing default constructor, and of
deferring the construction of the deque? As soon as you want to use
Something, you will trigger the delayed operation, and then you have to
be prepared for exceptions anyway.

--
Gerhard Menzl

Non-spammers may respond to my email address, which is composed of my
full name, separated by a dot, followed by at, followed by "fwz",
followed by a dot, followed by "aero".

Andre Kaufmann

unread,
Sep 20, 2008, 9:38:41 PM9/20/08
to
blargg wrote:
> [...]

>>> So, I see no problems in throwing c'tors usage. I agree, exceptions
>> I think there are perhaps some minor problems:
>>
>> a) A global object, which throws, will cause the application to
>> teminate / crash, without being able to handle the error.
>
> Then make the global object a smart pointer and don't allocate the
> object until it's first used. I consider global non-trivial objects bad
> style, because of this issue.

I agree. But there is still much code around simply using global objects
which use resources - e.g. a global object which wraps a mutex.
It's valid C++ even if it's perhaps not "good style - modern C++" and it
isn't that easy to write multi threaded safe global objects / singletons
anyways.

It would be fine if C++ would delay initialization of global objects
anyways, till they are used first time. But that is another story.

>> b) If one uses function pairs to be called in the
>> constructor / destructor and the constructor throws,
>> the destructor won't be called.
>>
>> If RAII objects are used to handle this there shouldn't be a problem,
>> but the developer has to be always aware of the
>> destructor not being called - it's simply another pitfall.
>
> But one has this problem regardless of whether the default constructor
> of any other classes throws, unless you're arguing that NO constructors
> should throw.

Yes. It's only a simple pitfall - I think quite too simple to run into.
E.g.:

class Foo
{
public:
Foo () { Lock (); }
~Foo() { Unlock(); }
};

No problem with that. But when I modify the object.

class Foo
{
public:
Foo () { Lock (); AcquireResources(); // might throw }
~Foo() { Unlock(); }
};

And AcquireResources() will throw an exception Unlock won't be called
anymore.
Yes - in a perfect C++ world Lock() Unlock() would have been called by a
RAII object. But I've seen too much code like that, without using RAII
and it's IMHO quite too easy to oversee that pitfall, even for
experienced C++ developers (which not necessarily have written that code)


>> c) How many developers expect constructors to throw ?
>
> Shouldn't a developer generally expect everything to throw, and only
> write code that can't handle exceptions in the cases where none are
> thrown and handling them is too costly/difficult? If things are written
> correctly, in most cases handling exceptions doesn't impose much of a
> burden.

Yes - agreed a developer should expect any function / constructor to
throw an exception. I only listened some problems, which might occur
when a constructor throws (if used in bad style code).
Perhaps code will be still safe, because the exceptions are handled
correctly, because the code is exception safe anyways.
But there might be a small significant difference in control flow, if
the developer doesn't always think about that each line of code could
fail by throwing an exception - even constructors.


I only have the impression, that many mediocre developers (perhaps even
experienced ones) when asked which of the following code lines may throw
an exception:

a) MyObject a[100];
b) foo(MyObject()); // Foo doesn't throw
c) MyObject a;
d) object++;
e) HandleResources();

would not say (without thinking it over) that each code line could throw
an exception and would tend to say e) might throw.


Surely a C++ expert wouldn't have to look at the code and say each code
line of code might throw an exception ;-).

>> I tend to initialize and use resources as late as possible.
>> Therefore I don't have commonly the need to allocate resources and
>> handle errors in the constructor anyways.
>
> You're not getting the full benefit of constructors. They are there to
> establish an object's invariant. The more stringent the invariant, the
> fewer possibilities member functions have to deal with.

But why would it be (generally) a benefit to allocate resources in the
constructor ?
(That doesn't mean that I don't initialize the state of the object
itself in the constructor)
I too allocate resources in objects constructors, where I have to and
where it would be too tedious to check the objects state in each member
function - e.g in lightweight objects.

Andre

Eugene Gershnik

unread,
Sep 20, 2008, 9:38:33 PM9/20/08
to
On Sep 19, 11:23 pm, Andrei Alexandrescu

<SeeWebsiteForEm...@erdani.org> wrote:
>
> The advantage of separating teardown from deallocation is that there are
> no more dangling pointers,

They can still happen if they point to external resources.

> and consequently even incorrect programs
> could achieve predictable, reproducible, and consequently debuggable
> behavior.

This assumes that the zombie state and the entire state machine it
brings to every object are implemented correctly. Which from my
experience with GC languages is as hard to do as to correctly use
manual memory management. If there is a mistake in zombification or
checking for a zombie-ness you might still have crashes or other forms
of UB when accessing an object in this state. Also note that usually
zombie state will be a rarely used and debugged one. It is also often
only accessed under specific timing conditions which makes the
'reproducible' argument weak.

> Once we accept the reality of "destroyed but not deallocated" objects,
> we need to define that state appropriately. That state claims no more
> resources than the bits of Foo alone, and must be detected by all member
> functions of the object such that they have defined behavior for that state.

Yep. And this is hard to get right (and maintain being right).

--
Eugene

Andre Kaufmann

unread,
Sep 20, 2008, 9:36:35 PM9/20/08
to
Joshua...@gmail.com wrote:
> On Sep 18, 1:22 pm, Jonathan Jones <hid...@this.is.invalid> wrote:


> [...]


>> What would the default constructor _usefully_ do that might lead to an
>> error requiring a throw? In your example, you could easily refactor
>> the code to only manipulate the OS mutex through a non-default
>> constructor, leaving the default constructor to perform simple, safe
>> initialization. In my opinion, this seems like better style, but
>> perhaps I'm overlooking something.
>

> [...]


> You will also have faster code. The extra function calls to initialize
> the object and the extra if statements to return early on a failure
> will perform slower than exceptions.

Depending on how good the compiler optimizes. Normally using exceptions
adds another overhead for each function to keep track of created objects
(exception stack), which might significantly increase the code size
(too). There might be additional code added for each throw statement used.

So I wouldn't bet on the code which uses exceptions to be really faster.

> [...]

Andre

Mathias Gaunard

unread,
Sep 20, 2008, 9:34:25 PM9/20/08
to
On 20 sep, 08:23, Andrei Alexandrescu <SeeWebsiteForEm...@erdani.org>
wrote:

> The advantage of separating teardown from deallocation is that there are
> no more dangling pointers, and consequently even incorrect programs
> could achieve predictable, reproducible, and consequently debuggable
> behavior. That way you get to use GC for what it's best at (recycling
> memory) and deterministic teardown for what it's best at (timely release
> of resources). In fact I'd go as far as saying that I think separating
> teardown from deallocation an essential ingredient in successfully
> marrying deterministic teardown with garbage collection. Other schemes
> could be conceived, and I've thought of a few, but at least those I
> could imagine lead to a mésalliance at most.

GC can be nothing but a memory allocation/deallocation strategy for
RAII.
For example, upon destruction of a dynamically allocated object, one
could choose to call the destructor, but not free the memory.
Better, if you think of mark & sweep for example, you could replace
the deallocation by marking the said object as dead. So GC only needs
to handle sweep, no reachability analysis is needed at all.

The object doesn't exist anymore, there is no need for any additional
"zombie" state within it. That state is available within the memory
block which has been marked as dead anyway.

Note that with this approach, though, you do not avoid dangling
pointers or whatever. It's just an optimization strategy for memory
management, correctness is still attained through RAII.

Andre Kaufmann

unread,
Sep 20, 2008, 9:36:38 PM9/20/08
to
Mathias Gaunard wrote:
> On 18 sep, 23:28, Andre Kaufmann <akfmn...@t-online.de> wrote:
>
>> c) How many developers expect constructors to throw ?
>
> Any one that ever heard about RAII, exceptions, STL or modern C++?
> (pick one)

So all code is written in modern C++ and all existing C++ code is 100%
exception safe ? At least my experience is that the world isn't perfect
and RAII and "modern C++" isn't that common as it should be.

>> I tend to initialize and use resources as late as possible.
>> Therefore I don't have commonly the need to allocate resources and
>> handle errors in the constructor anyways.
>
> Then you're really missing the whole point of constructors, I guess.
> They're made to initialize. If you want delayed initialization, just
> delay construction.

Perhaps there's a difference between initialization of variables /
states and the initialization / acquisition of resources ?

> Also, you're paying an extra overhead, since your object can always be
> in a "non-initialized-yet" state, which means your invariant really
> isn't what it ought to be.

Examples:

- The default constructor of an vector for example doesn't IIRC allocate
any memory.
- Why should an object which handles bitmaps allocate any bitmap
resources if it doesn't know the dimensions of the bitmap anyway ?

Andre

Dave Harris

unread,
Sep 20, 2008, 9:34:32 PM9/20/08
to
SeeWebsit...@erdani.org (Andrei Alexandrescu) wrote (abridged):

> Once we accept the reality of "destroyed but not deallocated"
> objects, we need to define that state appropriately. That
> state claims no more resources than the bits of Foo alone, and
> must be detected by all member functions of the object such that
> they have defined behavior for that state.

By "the bits of Foo alone", do you mean just sizeof(Foo)? Because I'd
have thought the zombie object would often be allowed to own other memory,
which would also be collected by the GC in due course rather than as part
of deterministic teardown. Releasing non-memory resources is often
necessary for correctness, and releasing memory resources is often (but
not always) just an optimisation (with GC).

Which means it makes sense (from a GC perspective) to make that owned
memory part of the class invariant and to allocate it in the constructor.
Which might fail and throw.


> A genuinely distinct state could be defined. A reasonable decision
> is to define that state as the default constructed state.

If an object does have a valid zombie state, then I agree it's a good
rule of thumb if the object can be constructed in that state. Although
usually it makes sense for the default constructor to do that, I am not
yet sure it's a universal rule.

For me one of the main ways a default constructor is special is that it
is used for arrays. So the question is whether code like:
Foo array[100];

should create 100 zombie Foos, or 100 usable ones. The people who don't
expect code to throw would presumably want zombies, but I can imagine
situations where having to follow this with a loop:

Foo array[100];
for (int i = 0; i < 100; ++i)
array[i].initialise();

would be inelegant. And of course the opposite, eg where an array of N
items is allocated in advance but only the first M items are in use and
need to consume resources.

I don't think we can have absolute rules here, only guidelines.

-- Dave Harris, Nottingham, UK.

--

Erik Wikström

unread,
Sep 20, 2008, 9:32:53 PM9/20/08
to
On 2008-09-20 08:23, Andrei Alexandrescu wrote:

> I asked the question because I'm coming from a slightly unusual angle.
> I've been using a garbage-collected environment for a while. When a GC
> is in the equation *together* with deterministic destruction,
> Interesting Things(TM) happen. One of them is that it's a great idea to
> separate teardown (destruction) from deallocation. For example:
>
> Foo * p = new Foo(...);
> ...
> delete p;
>
> In a GC environment, it makes a lot of sense if the delete statement
> only invokes Foo's destructor against *p and then... leaves the object
> alone in a "valid but non-resource-consuming" state.
>
> Such an approach is not foreign to many on this group; there's been many
> a discussion about what was descriptively called "zombie" object states.
>
> The advantage of separating teardown from deallocation is that there are
> no more dangling pointers, and consequently even incorrect programs
> could achieve predictable, reproducible, and consequently debuggable
> behavior. That way you get to use GC for what it's best at (recycling
> memory) and deterministic teardown for what it's best at (timely release
> of resources). In fact I'd go as far as saying that I think separating
> teardown from deallocation an essential ingredient in successfully
> marrying deterministic teardown with garbage collection. Other schemes
> could be conceived, and I've thought of a few, but at least those I
> could imagine lead to a mésalliance at most.

This really is not my area of expertise, but I fail to see how leaving
the object in an zombie-state will improve when it comes to dangling
pointers. After all, sooner or later the GC will come and collect the
used memory and it might be used by some other object. So at most you
manage to hide the fact that you are using an invalid pointer a bit longer.

I do agree that separating teardown from deallocation makes sense,
perhaps not only in a GC (I can imagine batching deallocations with
without a GC) but I still think that after the destructor has run the
object should be considered no longer existent.

--
Erik Wikström

Andrei Alexandrescu

unread,
Sep 20, 2008, 9:47:58 PM9/20/08
to

I'm highly surprised by this behavior of deque. I'd have hoped that STL
containers at least abide to a no-throwing-default-construction mantra.
The way that that deque is defined, it's impossible to conservatively
acquire the contents of one deque.

(When I say "conservative" in this context I mean it as an analogy to
"energy conservative" in physics, i.e. no effort is expended. Swapping
and moving are conservative, copying is not.)

For example, IMHO move construction should never, ever throw. I think
this is an immutable ideal, just like the energy conservation principle
is in physics. We start with a number of objects. We end with the same
number of objects, just in a different place. There shouldn't be at any
point any "effort" expended on allocating extra memory, aside from the
"friction" of moving bits around. Making the default constructor throw
makes moving via swap (the only move available for C++03)
non-conservative and consequently wasteful and potentially incorrect (if
peer code expects moving cannot engender exceptions). It is reasonable
to think that an object could embed a deque inside and, without risking
an exception, "absorbs" another deque.

Heck, if deque's constructor attempts to allocate memory (and
consequently throw), you can't even use the swap trick to ensure you
have completely emptied a deque. So picture the irony: _emptying_ a
deque via the swap trick could _throw_. This is a correctness issue that
I guess simply rules the swap trick out as a method of emptying a
container in C++03.


Andrei

--

David Abrahams

unread,
Sep 20, 2008, 9:53:03 PM9/20/08
to

on Thu Sep 18 2008, Andre Kaufmann <akfmnews-AT-t-online.de> wrote:

> a) A global object, which throws, will cause the application to
> teminate / crash, without being able to handle the error.

So it's OK for this global object to throw

Foo x(5);

but not this one?

Foo x;

If so, why?

--
Dave Abrahams
BoostPro Computing
http://www.boostpro.com

Andre Kaufmann

unread,
Sep 20, 2008, 9:58:54 PM9/20/08
to
analizer wrote:
> On Sep 19, 1:28 am, Andre Kaufmann <akfmn...@t-online.de> wrote:
>> a) A global object, which throws, will cause the application to
>> teminate / crash, without being able to handle the error.
>
> What about to use singletons instead of "global objects"? Global
> objects cause too many pitfalls such as initialization and linkage
> orders etc. For singletons you can handle exceptions in c'tors
> gracefully. Also this will allow to delay object initialization, it
> will be initialized when it will be used first time.

Agreed. But there's too much code around simply using global objects and
singletons aren't that simple to handle in a multi threaded application.
I too don't use (complex) global objects, but unfortunately the world
isn't perfect ;-).

> [...]

>> c) How many developers expect constructors to throw ?
>
> All (ideally). If class method (including c'tor) throws exception,
> then this should be reflected in code comments in header file. Anyone
> who uses the class method should learn how this method works, for
> example by reading comments to this method.

O.k. I agree, in modern C++ it's no problem to throw an exception in the
constructor and I would agree too that it's also a good style in modern
C++ to throw an exception in the constructor.

I only listed some problems, which might occur in old style, but still
common C++ code ;-)

> Dmitry

Andre

Mathias Gaunard

unread,
Sep 21, 2008, 5:35:30 PM9/21/08
to
On 21 sep, 03:36, Andre Kaufmann <akfmn...@t-online.de> wrote:

> So I wouldn't bet on the code which uses exceptions to be really faster.

Exceptions are faster than if/else branches and return codes, there
are no tests or branching.
That's the whole point of the Itanium Exception ABI; they're zero-
overhead (or quasi-zero, some might say)

Yes, it generates bigger code, however.

Andrei Alexandrescu

unread,
Sep 21, 2008, 5:35:14 PM9/21/08
to

I walk along the same reasoning myself. A default-constructed, empty
container should not automatically assume I'll need to fill it right
away and be eager to preallocate (and refuse to even come into existence
if it can't right then and there)! This is quite clearly a design mistake.

But the example with the mutex makes a very strong counter-argument,
particularly since safely initializing lazily a mutex is not an easy
task itself. On the other hand, I don't feel that if I just create a
mutex (e.g. as a member) the mutex should assume my purpose is to
acquire it and therefore eagerly allocate resources...


Andrei

Andrei Alexandrescu

unread,
Sep 21, 2008, 5:42:07 PM9/21/08
to

I also agree it's fine and good style to throw from a constructor. I
only wonder (without being strongly convinced) whether throwing from the
constructor of an un-customized object is good.

Construction with parameters is a statement of intent. I, $DEITY, am
creating this object with specific parameters that give it personality
and make it "special", end expect the object to take shape according to
those parameters. If not possible, an exception is in order.

Construction without parameters seems in many cases to me a
"personalization to come later" statement, an as-of-yet-unexpressed
intent to make use of that object in accordance to its type, and also a
desire to keep options open and therefore preserve the object in an
inert state. Since often objects don't have all relevant data to fill
their subobjects during construction, I think it's only natural to
initialize them parameterlessly (and expect to not throw), to fill them
later on. Examples include all sorts of serialization scenarios
(including reading from databases, files, sockets, and so on). Also, if
you sorted a vector<deque<int> > and wanted to swap the pivot into a
temporary, is it reasonable for that temporary's non-argument
initialization to throw?


Andrei

Andrei Alexandrescu

unread,
Sep 21, 2008, 5:47:09 PM9/21/08
to
Dave Harris wrote:
> SeeWebsit...@erdani.org (Andrei Alexandrescu) wrote (abridged):
>> Once we accept the reality of "destroyed but not deallocated"
>> objects, we need to define that state appropriately. That
>> state claims no more resources than the bits of Foo alone, and
>> must be detected by all member functions of the object such that
>> they have defined behavior for that state.
>
> By "the bits of Foo alone", do you mean just sizeof(Foo)? Because I'd
> have thought the zombie object would often be allowed to own other memory,
> which would also be collected by the GC in due course rather than as part
> of deterministic teardown. Releasing non-memory resources is often
> necessary for correctness, and releasing memory resources is often (but
> not always) just an optimisation (with GC).

That's an interesting take. I was thinking that the destructor of an
object should set to null all pointer members. That way, even if there
are still dangling pointers referring to the zombie object, the other
subobjects reachable through the object can be freed properly (assuming
of course there are no dangling pointers to _them_!)

> Which means it makes sense (from a GC perspective) to make that owned
> memory part of the class invariant and to allocate it in the constructor.
> Which might fail and throw.

Within that approach, I agree.

>> A genuinely distinct state could be defined. A reasonable decision
>> is to define that state as the default constructed state.
>
> If an object does have a valid zombie state, then I agree it's a good
> rule of thumb if the object can be constructed in that state. Although
> usually it makes sense for the default constructor to do that, I am not
> yet sure it's a universal rule.

Me neither. I only see it as a sort of a compromise between simplicity
and correctness.

> For me one of the main ways a default constructor is special is that it
> is used for arrays. So the question is whether code like:
> Foo array[100];
>
> should create 100 zombie Foos, or 100 usable ones. The people who don't
> expect code to throw would presumably want zombies, but I can imagine
> situations where having to follow this with a loop:
>
> Foo array[100];
> for (int i = 0; i < 100; ++i)
> array[i].initialise();
>
> would be inelegant. And of course the opposite, eg where an array of N
> items is allocated in advance but only the first M items are in use and
> need to consume resources.
>
> I don't think we can have absolute rules here, only guidelines.

Well I'm glad you brought that up too. The thing with
default-constructed arrays that could throw has also irked me for quite
a while. There's all that protocol for constructing arrays that led in
part to the "destructors can't throw" mantra. So many people have taken
that mantra non-critically, it has effectively petrified into a dogma.
But IMHO destructors should throw if they smell anything fishy. I'll
keep that for a different post.

Note that I don't see a need for the loop initializing elements of the
array. In my view, default-constructed objects should be usable out of
the box.


Andrei

Andrei Alexandrescu

unread,
Sep 21, 2008, 5:47:01 PM9/21/08
to
Eugene Gershnik wrote:
> On Sep 19, 11:23 pm, Andrei Alexandrescu
> <SeeWebsiteForEm...@erdani.org> wrote:
>> The advantage of separating teardown from deallocation is that there are
>> no more dangling pointers,
>
> They can still happen if they point to external resources.
>
>> and consequently even incorrect programs
>> could achieve predictable, reproducible, and consequently debuggable
>> behavior.
>
> This assumes that the zombie state and the entire state machine it
> brings to every object are implemented correctly. Which from my
> experience with GC languages is as hard to do as to correctly use
> manual memory management. If there is a mistake in zombification or
> checking for a zombie-ness you might still have crashes or other forms
> of UB when accessing an object in this state. Also note that usually
> zombie state will be a rarely used and debugged one. It is also often
> only accessed under specific timing conditions which makes the
> 'reproducible' argument weak.

Au contraire, the fact that zombie state is the default-constructed
state makes it very easy to both attain and check. It only requires
careful writing of the destructor (it would be nicer still if the
compiler planted the appropriate code itself). Also, since all member
functions must check for the default-constructed state to start with,
they won't have any trouble in dealing with zombie objects.

>> Once we accept the reality of "destroyed but not deallocated" objects,
>> we need to define that state appropriately. That state claims no more
>> resources than the bits of Foo alone, and must be detected by all member
>> functions of the object such that they have defined behavior for that state.
>
> Yep. And this is hard to get right (and maintain being right).

Well if you implemented std::vector, wouldn't you check for the empty
vector (begin == end == store_end) anyway? Would you argue that empty
states bring trouble to implementing std::vector?


Andrei

Andrei Alexandrescu

unread,
Sep 21, 2008, 5:44:04 PM9/21/08
to

The GC recycles memory when by definition the program cannot reach it.
This excludes a number of oddities such as forging pointers, but by and
large it can be used as an axiom.

Andrei

--

Joshua...@gmail.com

unread,
Sep 21, 2008, 5:45:37 PM9/21/08
to
On Sep 20, 6:36 pm, Andre Kaufmann <akfmn...@t-online.de> wrote:

> JoshuaMaur...@gmail.com wrote:
> > On Sep 18, 1:22 pm, Jonathan Jones <hid...@this.is.invalid> wrote:
> > [...]
> >> What would the default constructor _usefully_ do that might lead to an
> >> error requiring a throw? In your example, you could easily refactor
> >> the code to only manipulate the OS mutex through a non-default
> >> constructor, leaving the default constructor to perform simple, safe
> >> initialization. In my opinion, this seems like better style, but
> >> perhaps I'm overlooking something.
>
> > [...]
> > You will also have faster code. The extra function calls to initialize
> > the object and the extra if statements to return early on a failure
> > will perform slower than exceptions.
>
> Depending on how good the compiler optimizes. Normally using exceptions
> adds another overhead for each function to keep track of created objects
> (exception stack), which might significantly increase the code size
> (too). There might be additional code added for each throw statement used.
>
> So I wouldn't bet on the code which uses exceptions to be really faster.

This is the way they used to be implemented. For example, Visual
Studios 2003 implements it that way.

On any good compiler, during normal course of operation, there is
absolutely no additional work. When an exception is thrown, it goes to
a global lookup table based on the current program counter value. This
table is built at compile time. Each entry points to a block of code
to properly tear down the currently constructed portion of the stack
frame, then pass control to the next compiler-made exception handler
of the containing stack frame, or to a user's catch block.

Now, arguably, it won't result in terribly faster code in most uses.
We're just talking about a couple less if statements, but that's a
couple less if statements on the normal execution path than what we
had before. Less actual machine instructions run, and higher
instruction cache hit rates.

Andre Kaufmann

unread,
Sep 21, 2008, 5:48:18 PM9/21/08
to
David Abrahams wrote:
> on Thu Sep 18 2008, Andre Kaufmann <akfmnews-AT-t-online.de> wrote:
>
>> a) A global object, which throws, will cause the application to
>> teminate / crash, without being able to handle the error.
>
> So it's OK for this global object to throw
>
> Foo x(5);
>
> but not this one?
>
> Foo x;
>
> If so, why?

I think the effect is the same, regardless which constructor is called -
if it throws an exception.
And therefore no constructor of global instantiated objects should throw
exceptions - regardless if it's a bad style to use global objects or
not, since AFAIK there's no portable possibility for an application to
handle such exceptions - at least not if the code can't be changed.

Andre

--

Mathias Gaunard

unread,
Sep 21, 2008, 5:58:56 PM9/21/08
to
On 21 sep, 03:36, Andre Kaufmann <akfmn...@t-online.de> wrote:

> So all code is written in modern C++ and all existing C++ code is 100%
> exception safe ? At least my experience is that the world isn't perfect
> and RAII and "modern C++" isn't that common as it should be.

So you propose that instead of fixing it, we keep in mind that most
code is ill-designed and that broken idioms should still be supported
by newly-designed code?


> Perhaps there's a difference between initialization of variables /
> states and the initialization / acquisition of resources ?

Well, when resource acquisition is initialization, there is none.


> Examples:
>
> - The default constructor of an vector for example doesn't IIRC allocate
> any memory.

A vector is a sequence of elements. Having zero element is a perfectly
valid state.
The default constructor of vector makes it have zero element.
Allocating memory is not required for the object to function properly
then.
It may still allocate memory however (for future insertions) the
standard doesn't prevent it.


> - Why should an object which handles bitmaps allocate any bitmap
> resources if it doesn't know the dimensions of the bitmap anyway ?

The object probably should be given that kind of information as
arguments to its constructor.

Mathias Gaunard

unread,
Sep 21, 2008, 5:58:56 PM9/21/08
to
On 21 sep, 03:38, Andre Kaufmann <akfmn...@t-online.de> wrote:

> class Foo
> {
> public:
> Foo () { Lock (); AcquireResources(); // might throw }
> ~Foo() { Unlock(); }
>
> };
>
> And AcquireResources() will throw an exception Unlock won't be called
> anymore.

It is true that constructors need special care for exception-safety.

One could write
Lock(); try { AcquireResources(); } catch(...) { Unlock(); throw; }

But if you think about it, you're gonna end up duplicating a lot of
code here. Put the lock object as a member and problem solved.


> I only have the impression, that many mediocre developers (perhaps even
> experienced ones) when asked which of the following code lines may throw
> an exception:
>
> a) MyObject a[100];
> b) foo(MyObject()); // Foo doesn't throw
> c) MyObject a;
> d) object++;
> e) HandleResources();
>
> would not say (without thinking it over) that each code line could throw
> an exception and would tend to say e) might throw.

That really depends on whether the code invoked is nothrow or not.
So it's only a matter of realizing what code is invoked and knowing
the properties of the types and functions which are being manipulated.

Mathias Gaunard

unread,
Sep 21, 2008, 5:58:56 PM9/21/08
to
On 21 sep, 03:47, Andrei Alexandrescu <SeeWebsiteForEm...@erdani.org>
wrote:

> For example, IMHO move construction should never, ever throw.

That's fine and all, but that's simply not always possible.
What if my object needs to have its address maintained in some sort of
container? If that container is a std::set, for example, I need to
remove the old address and put a new one. That might throw.

It could be argued, however, that it's not that likely to throw, so
one might as well abort if an exception occurs to make the operation
nothrow.


> Heck, if deque's constructor attempts to allocate memory (and
> consequently throw), you can't even use the swap trick to ensure you
> have completely emptied a deque. So picture the irony: _emptying_ a
> deque via the swap trick could _throw_. This is a correctness issue that
> I guess simply rules the swap trick out as a method of emptying a
> container in C++03.

It's not deque-specific.
All default constructors of containers are not required to be nothrow.

Electronic Arts has asked the standard to change that, however,
showing their own EASTL as an improvement upon the STL.

David Abrahams

unread,
Sep 22, 2008, 8:27:41 PM9/22/08
to

on Sat Sep 20 2008, Andrei Alexandrescu <SeeWebsiteForEmail-AT-erdani.org> wrote:

> I'm highly surprised by this behavior of deque. I'd have hoped that STL
> containers at least abide to a no-throwing-default-construction
> mantra.

The exception guarantees given by the standard are pretty much the
minimal set required in order to get useful work done. See
http://www.boost.org/community/exception_safety.html#footnote13

> The way that that deque is defined, it's impossible to conservatively
> acquire the contents of one deque.
>
> (When I say "conservative" in this context I mean it as an analogy to
> "energy conservative" in physics, i.e. no effort is expended. Swapping
> and moving are conservative, copying is not.)

And what do you mean by "acquire?" Seems to me I can move from one
deque into another; that's conservative.

> For example, IMHO move construction should never, ever throw. I think
> this is an immutable ideal, just like the energy conservation principle
> is in physics. We start with a number of objects. We end with the same
> number of objects, just in a different place. There shouldn't be at any
> point any "effort" expended on allocating extra memory, aside from the
> "friction" of moving bits around. Making the default constructor throw
> makes moving via swap (the only move available for C++03)
> non-conservative

Hardly. First of all, swap doesn't use the default constructor at all.
Secondly, in C++03, swap should be individually defined for all
user-defined types, conservatively whenever possible. Third, in C++0x
there's a generic swap that is written in terms of move, which should be
conservative.

> and consequently wasteful and potentially incorrect (if
> peer code expects moving cannot engender exceptions). It is reasonable
> to think that an object could embed a deque inside and, without risking
> an exception, "absorbs" another deque.

That's just move semantics AFAICT, which should be nonthrowing. If you
mean something else, please explain.

> Heck, if deque's constructor attempts to allocate memory (and
> consequently throw), you can't even use the swap trick to ensure you
> have completely emptied a deque.

Of course you can. It just might throw an exception. So what?

> So picture the irony: _emptying_ a deque via the swap trick could
> _throw_.

Sorry, I'm not getting much of a picture. Your swap trick looks like:

deque<int>().swap(d);

Plain as day you are constructing an object whose constructor may throw
an exception. Personally I'm more fond of

d.clear();

for that purpose.

> This is a correctness issue that I guess simply rules the swap trick
> out as a method of emptying a container in C++03.

I can't imagine why you'd say that.

--
Dave Abrahams
BoostPro Computing
http://www.boostpro.com

[ See http://www.gotw.ca/resources/clcm.htm for info about ]

Dave Harris

unread,
Sep 22, 2008, 8:26:31 PM9/22/08
to
SeeWebsit...@erdani.org (Andrei Alexandrescu) wrote (abridged):
> Au contraire, the fact that zombie state is the default-constructed
> state makes it very easy to both attain and check. It only requires
> careful writing of the destructor (it would be nicer still if the
> compiler planted the appropriate code itself). Also, since all
> member functions must check for the default-constructed state to
> start with, they won't have any trouble in dealing with zombie
> objects.
It sounds to me as though that approach permits resurrection. If default
construction produces a zombie, and first use initialises the object
properly, and destruction puts the object back into its default
constructed state, then a use after destruction would reinitialise it.

This strikes me as potentially bad. At best it conceals bugs. At worst
the resurrected object would not get destroyed a second time, and would
leak resources and have other problems (eg deadlocks due to locks not
getting released).

In order to prevent resurrection the object needs to know the difference
between a new default-constructed object and a destroyed one. Code which
handles one case correctly won't necessarily handle both.

Have you thought about the pImpl idiom? Any object can potentially be
implemented as a pImpl, in which case it needs memory for the Impl
whether or not it needs other resources.

Presumably you'd have code like:

// Wrapper.h
class Impl;

class Wrapper {
public:
Wrapper() : pImpl( NULL ), isDestroyed(false) {}
~Wrapper() { destroy(); }
void destroy() { pImpl = NULL; isDestroyed = true; }

int simpleAccessor() const;
//...

private:
mutable Impl *pImpl;
bool isDestroyed;
void ensureInitalised();
};

// Wrapper.cpp
#include "Wrapper.h"
#include "Impl.h"

void Wrapper::ensureInitalised() const {
if (pImpl)
return; // Not first use.
if (isDestroyed)
throw "Use after destroy"; // Disallow resurrection.
pImpl = new Impl; // Initialise; may throw.
}

int Wrapper::simpleAccessor() const {
ensureInitialised();
return pImpl->simpleAccessor();
}

Is this what you mean by good style? It strikes me as over-complex.

I especially dislike the need to use the "mutable" keyword. I'd be
tempted instead by code like:

int Wrapper::simpleAccessor() const {
return pImpl ? pImpl->simpleAccessor() : 0;
}

But this is a bit ad hoc and error-prone. (It is also an example of
checking for the default-constructed state but not for the destroyed
state.)

If instead we strength the class invariant by saying the Impl is always
allocated, the code gets much simpler and cleaner.

-- Dave Harris, Nottingham, UK.

--

Andrei Alexandrescu

unread,
Sep 22, 2008, 8:29:34 PM9/22/08
to
Mathias Gaunard wrote:
> On 21 sep, 03:47, Andrei Alexandrescu <SeeWebsiteForEm...@erdani.org>
> wrote:
>
>> For example, IMHO move construction should never, ever throw.
>
> That's fine and all, but that's simply not always possible.
> What if my object needs to have its address maintained in some sort of
> container? If that container is a std::set, for example, I need to
> remove the old address and put a new one. That might throw.

That actually suggests to me that std::set should have a conservative
primitive replace that replaces the value sitting at a given iterator
with a new value, without any node allocation. But that's hard because
in C++ defining swapping and move construction modularly are very difficult.

> It could be argued, however, that it's not that likely to throw, so
> one might as well abort if an exception occurs to make the operation
> nothrow.

Since the "move should not throw" is a restriction it will by necessity
limit the number of correct programs. The question is whether the
benefits of the restriction outweigh that loss.

>> Heck, if deque's constructor attempts to allocate memory (and
>> consequently throw), you can't even use the swap trick to ensure you
>> have completely emptied a deque. So picture the irony: _emptying_ a
>> deque via the swap trick could _throw_. This is a correctness issue that
>> I guess simply rules the swap trick out as a method of emptying a
>> container in C++03.
>
> It's not deque-specific.
> All default constructors of containers are not required to be nothrow.
>
> Electronic Arts has asked the standard to change that, however,
> showing their own EASTL as an improvement upon the STL.

I'm of the same opinion as them.


Andrei

David Abrahams

unread,
Sep 22, 2008, 8:25:16 PM9/22/08
to

on Sun Sep 21 2008, Andrei Alexandrescu <SeeWebsiteForEmail-AT-erdani.org> wrote:

> Also, if you sorted a vector<deque<int> > and wanted to swap the pivot
> into a temporary, is it reasonable for that temporary's non-argument
> initialization to throw?

Yeah, I think it's reasonable. It's worth noting that my standard
library implementation never needs to default-construct a pivot.

I'm guessing you want to get a nonthrowing sort based on nonthrowing
move and swap operations?

Of course, if you believe that "all types are Regular," it isn't even a
question: default construction, move, and swap are all nonthrowing in
that case.

--
Dave Abrahams
BoostPro Computing
http://www.boostpro.com

[ See http://www.gotw.ca/resources/clcm.htm for info about ]

Eugene Gershnik

unread,
Sep 22, 2008, 8:27:51 PM9/22/08
to
Andrei Alexandrescu wrote:
>
> Au contraire, the fact that zombie state is the default-constructed
> state makes it very easy to both attain and check. It only requires
> careful writing of the destructor (it would be nicer still if the
> compiler planted the appropriate code itself).

Only? Just like avoiding memory leaks only requires careful writing of
deallocation calls. ;-)
My point is that there is no automatic guarantee that
a) the zombie and default are the same and
b) the code that tests for zombieness/defaultness is correct and
called everywhere it is needed
The programmer can certainly try to ensure these but this is precisely
the error-prone thing that needs to be tested. And, as I said, live
objects in zombie state (i.e. the actual state after the destructor
had run, not the one you hope them to be) are often hard to get during
normal program run.

> Also, since all member
> functions must check for the default-constructed state to start with,
> they won't have any trouble in dealing with zombie objects.

Not all of them. First of all private helpers do not generally need to
check. Second many functions do not need an explicit check *today*
since their behavior is well defined for any state. Using your example
below, consider vector::size(). If implemented as end - begin it
doesn't need any check for if (begin == 0). The problem is that such
assumptions can easily become invalid *tomorrow* during maintenance.

> Well if you implemented std::vector, wouldn't you check for the empty
> vector (begin == end == store_end) anyway? Would you argue that empty
> states bring trouble to implementing std::vector?

This is a bad example. Vector interface and implementation are
effectively frozen and it is unlikely to undergo any significant
changes in any standard library implementation. Second, vector is as
easy an example as

void * p = malloc(42);
free(p);

but this is hardly a proof that people don't routinely make mistakes
with manual allocation/deallocation.
Third, vector makes the user responsible to check for default state.
This actually makes this example to work against you since lots of
code would break if given a default constructed vector where it
expects a populated one. People routinely forget to check the size()
before calling &vec[0], *begin() etc.

At the end I can only speak from my own experience and it tells me
that quite a significant portion of my Java and C# bugs came from
objects that were incorrectly zombified or checked for zombie-ness.

--
Eugene

blargg

unread,
Sep 22, 2008, 8:25:09 PM9/22/08
to
In article <K7IwF...@beaver.cs.washington.edu>, Andrei Alexandrescu
<SeeWebsit...@erdani.org> wrote:

[...]


> But the example with the mutex makes a very strong counter-argument,
> particularly since safely initializing lazily a mutex is not an easy
> task itself. On the other hand, I don't feel that if I just create a
> mutex (e.g. as a member) the mutex should assume my purpose is to
> acquire it and therefore eagerly allocate resources...

If you really want delayed construction, use something like this, rather
than burdening every class implementation with the equivalent. The mutex
and other thread examples might not be applicable due to synchronization,
but this should apply to objects used by one thread.

template<class T>
Delayed {
// could instead have aligned storage by value, to avoid
// freestore operations
T* t;
public:
Delayed() : t( 0 ) { }
~Delayed() { delete t; }

T* operator -> ()
{
if ( !t )
t = new T;
return t;
}

// ...
// operator *, etc.
// perhaps also a construct() that allows specifying parameters
};

David Abrahams

unread,
Sep 22, 2008, 8:34:49 PM9/22/08
to

on Sun Sep 21 2008, Andrei Alexandrescu <SeeWebsiteForEmail-AT-erdani.org> wrote:

> I don't feel that if I just create a mutex (e.g. as a member) the
> mutex should assume my purpose is to acquire it and therefore eagerly
> allocate resources...

I can't believe you have a good reason to create a mutex member that you
don't ever intend to acquire.

--
Dave Abrahams
BoostPro Computing
http://www.boostpro.com

[ See http://www.gotw.ca/resources/clcm.htm for info about ]

Erik Wikström

unread,
Sep 22, 2008, 8:35:47 PM9/22/08
to

Doesn't that go against the whole idea of having the default constructor
create objects in zombie states? To me a zombie-state is one where the
object says "not usable, have to initialise first" and any operations on
them either either throws or they are just NOOPs. Of course this does
not have to be true for all objects, many objects does not require any
initialisation to begin with but a lot do, and they are usually the same
ones that could possibly throw in the constructor.

--
Erik Wikström

Andre Kaufmann

unread,
Sep 22, 2008, 8:34:49 PM9/22/08
to
Mathias Gaunard wrote:
> On 21 sep, 03:38, Andre Kaufmann <akfmn...@t-online.de> wrote:
>
>> class Foo
>> {
>> public:
>> Foo () { Lock (); AcquireResources(); // might throw }
>> ~Foo() { Unlock(); }
>>
>> };
>>
>> And AcquireResources() will throw an exception Unlock won't be called
>> anymore.
>
> It is true that constructors need special care for exception-safety.
>
> One could write
> Lock(); try { AcquireResources(); } catch(...) { Unlock(); throw; }
>
> But if you think about it, you're gonna end up duplicating a lot of
> code here. Put the lock object as a member and problem solved.

As I have written:

"Yes - in a perfect C++ world Lock() Unlock() would have been called by
a RAII object"

I only stated, that it's a pitfall - even if helper objects will reduce
the chance to run into such a problem, since not every developer likes
to use an extra helper object, just to wrap 2 function calls. I try to
use helper object, but sometimes I don't too.

Andre

Marsh Ray

unread,
Sep 22, 2008, 8:35:44 PM9/22/08
to
On Sep 20, 8:47 pm, Andrei Alexandrescu

<SeeWebsiteForEm...@erdani.org> wrote:
> Heck, if deque's constructor attempts to allocate memory (and
> consequently throw), you can't even use the swap trick to ensure you
> have completely emptied a deque.

Only if your code is depending on this not throwing, which is a small
minority of what I write in recent years.

> So picture the irony: _emptying_ a
> deque via the swap trick could _throw_. This is a correctness issue that
> I guess simply rules the swap trick out as a method of emptying a
> container in C++03.

I agree that it's ironic, but don't see how it's a general correctness
issue.

I gave up trying to track what throw and what doesn't, and have been
much happier for it. I assume nearly anything could throw (or later be
modified to), and tend to only try/catch around calls from C code,
destructors, and when there's the need to retry an operation.

- Marsh

David Abrahams

unread,
Sep 22, 2008, 8:37:15 PM9/22/08
to

on Fri Sep 19 2008, Andrei Alexandrescu <SeeWebsiteForEmail-AT-erdani.org> wrote:

> I asked the question because I'm coming from a slightly unusual angle.
> I've been using a garbage-collected environment for a while. When a GC
> is in the equation *together* with deterministic destruction,
> Interesting Things(TM) happen. One of them is that it's a great idea to
> separate teardown (destruction) from deallocation. For example:
>
> Foo * p = new Foo(...);
> ...
> delete p;
>
> In a GC environment, it makes a lot of sense if the delete statement
> only invokes Foo's destructor against *p and then... leaves the object
> alone in a "valid but non-resource-consuming" state.

E.g. all bits zero?

> Such an approach is not foreign to many on this group; there's been
> many a discussion about what was descriptively called "zombie" object
> states.

To accomodate "zombie" states you may need to expand the definition of
what constitute "valid" states for the object or the ways zombie-ness
can be reached, weakening the class' invariants.

> The advantage of separating teardown from deallocation is that there
> are no more dangling pointers,

Not so fast. If you're willing to define a pointer to a zombie as
non-dangling, then yeah, OK. But a pointer to a zombie is distinctly
less safe to use than a pointer to a regular object.

You also have to be aware of the limits of the guarantees you can get
from that. Sure, the object's immediate bits do not get replaced by
those of some other live object until all the pointers to it are gone
and GC comes along to sweep up the memory. But you can still walk off
either edge of the object and read/write.

> and consequently even incorrect programs could achieve predictable,
> reproducible, and consequently debuggable behavior.

Provided they eschew pointer arithmetic, yeah. There are probably other
caveats I haven't thought of.

If you are going to use GC in this way, you need to decide whether you
want it to be part of the language/system definition or simply an
expression of undefined behavior you can choose (e.g. for your debug
builds). I strongly favor the latter, for several reasons:

1. I don't want to be forced to accept the performance cost of these
checks every time a pointer is dereferenced.

2. If the compiler is generating the checks, I don't want to be forced
to accept the storage cost of representing a "torn-down" state so
that it can be detected (you need one more bit for that in general,
and I doubt the compiler can figure out how to hide that bit
reliably in general).

3. If the user is writing the checks, I don't want to be forced to
accept the programming and maintainance cost of representing and
detecting the zombie state throughout my code. It amounts to
defensive programming, and IMO it's much easier to write correct code
without such checking in the way.

4. You need to choose a defined behavior when the checks fail. I
suppose aborting is OK, but most language designers are inclined to
throw exceptions from these operations, a choice which is fraught
with peril for reason-ability and correctness
(http://groups.google.com/group/comp.lang.c++.moderated/msg/00b0361b15ae2d1e).

Finally, if you choose to throw, then in order to make the default
ctor nonthrowing you've just made all the other operations
potentially throwing. That's a very bad tradeoff.

So, IMO, GC makes good sense as a debugging tool for C++, but not as a
core language feature.

> That way you get to use GC for what it's best at (recycling memory)
> and deterministic teardown for what it's best at (timely release of
> resources). In fact I'd go as far as saying that I think separating
> teardown from deallocation an essential ingredient in successfully
> marrying deterministic teardown with garbage collection.

I think it's known and well-accepted by now that GC finalizers cannot in
general call destructors, which basically leads one directly to the same
conclusion.

> Once we accept the reality of "destroyed but not deallocated" objects,
> we need to define that state appropriately.

Not necessarily ;-). Leaving certain things undefined in the language
can have advantages, not least that the implementation is free to be
efficient rather than slow and "checked."

> That state claims no more resources than the bits of Foo alone, and
> must be detected by all member functions of the object such that they
> have defined behavior for that state.

You're scaring me again. Sounds.... slooooow.

<snip>

> d) Non-throwing default constructors occasionally facilitate writing
> algorithms. For example, in Hoare's partition it's convenient to store
> the pivot in a local temporary. The temporary is default-constructed and
> then only swapped around. In other words, non-throwing default
> constructors allow writing "conservative" algorithms on collections that
> do not create any new value, but do use temporary memory to shuffle
> values around.

Yes, it can be convenient, and aside from the fact that uninitialized
built-ins are also zombies ("extending the semantics of C to
user-defined types"), I understand that to be Stepanov's reason for
insisting on nonthrowing default-constructibility in Regular Types.

However, default-constructibility is not a *huge* convenience in any
algorithm implementation I've seen, especially not partition, and I
value the correctness guarantees I can get from the compiler over this
convenience.

> There are disadvantages too, for example it becomes impossible to
> distinguish a legit default-constructed object from one that has had a
> life and kicked the bucket. For example in the mutex case it may make
> sense to define a default-constructed mutex one on which initialization
> did succeed, and a zombie one a mutex that is obliterated with an
> illegal bit pattern. (A more portable solution would need to add a bool
> to the object state.)
>
> I'd be interesting in hearing further opinions on how garbage collection
> interacts with defining object states.

IMO there the most obvious interactions have to do with peoples'
expectations. People in general expect to be able to forget about
object lifetime when they have GC, and you just can't, in general. If
you do forget about it completely, you undermine class authors who
reasonably expect their dtors to be called. If I write a generic
algorithm that leaks a vector<T> and T turns out to be mutex, the
program is broken.

As far as I can tell, you need to get the fact that an object manages
non-memory resources into the type system in order to fulfill peoples'
expectations to actually leak stuff, and the real opportunities to do so
will be far fewer than many people expect. We certainly don't have a
reliable programming model for doing it in C++ as defined today, which
is the main reason I opposed the recent proposals for adding GC to the
language.

This problem also crops up when you have true shared ownership.
You still need to know when to do teardown, so you need to duplicate the
reference counting that most people think is eliminated by GC.

In sum:

* I find the correctness benefits of a deterministic-teardown GC system
with resourceless default construction to be dubious at best -- yes,
there are runtime safety gains but they may be offset compile-time and
code reasoning costs.

* I fear that such a system will fail miserably to fulfill people's
expectations for what a GC'd language can provide. If we're lucky
that would lead to almost nobody taking advantage of the system. If
we're unlucky, people will write code as though the language can
fulfill their expectations, when really it can't. Think: exception
specifications.

Cheers,

--
Dave Abrahams
BoostPro Computing
http://www.boostpro.com

[ See http://www.gotw.ca/resources/clcm.htm for info about ]

Andre Kaufmann

unread,
Sep 22, 2008, 8:33:38 PM9/22/08
to
Joshua...@gmail.com wrote:
> [...]

> This is the way they used to be implemented. For example, Visual
> Studios 2003 implements it that way.
>
> On any good compiler, during normal course of operation, there is

Hm, implies the previous compiler isn't quite good ?
By the way this compiler eventually had to track non linear control flow
too, caused by raised structured exceptions (which are not standard C++).

> absolutely no additional work. When an exception is thrown, it goes to
> a global lookup table based on the current program counter value. This
> table is built at compile time. Each entry points to a block of code

Can you really map each line of code to a static lookup table, without
keeping track which object has been constructed or not ?

One (lame) example:

void foo()
{

if (a) goto v2;
v1:
object a1;
v2:
object a2;
v3:
}

How does (such a) the compiler know at v3: if object a1() has been
constructed or not ?


> to properly tear down the currently constructed portion of the stack
> frame, then pass control to the next compiler-made exception handler
> of the containing stack frame, or to a user's catch block.
>
> Now, arguably, it won't result in terribly faster code in most uses.
> We're just talking about a couple less if statements, but that's a
> couple less if statements on the normal execution path than what we
> had before. Less actual machine instructions run, and higher
> instruction cache hit rates.

Well, depends on the overall code size. Which is the compiler, which has
that well optimized, table based, exception handling ? I would like to
have a look at the generated assembly code.

My experience is that my assumptions about the speed of code haven't
been that accurate today as they have been before - due to multiple
cores, parallel code execution, prefetching - new optimizing strategies
like profile guided optimization etc.

Andre

Erik Wikström

unread,
Sep 22, 2008, 8:37:11 PM9/22/08
to

I just assumed that the GC would be allowed to collect any object whose
destructor had run, kind of like mark and sweep. This way you don't have
to do the reachability scan as often since it would only be for litter-
collection, and you would also know that the destructors of those non-
reachable objects found had not been run.

--
Erik Wikström

Andrei Alexandrescu

unread,
Sep 23, 2008, 8:28:44 AM9/23/08
to

That's pretty much why I dislike the term zombie for a terminated
object. To me that object should be eminently usable, just that it is in
an empty state where it consumes no resources beyond its own bits.

In that view of the world, objects remain infinitely-lived as they've
always been in a GC system. On top of that, destructors just make sure
that additional resources that objects occasionally acquire are released
at specific boundaries. To be honest, the more I think about that model,
the more I like it. :o)


Andrei


--

Joshua...@gmail.com

unread,
Sep 23, 2008, 9:08:29 PM9/23/08
to
On Sep 22, 5:33 pm, Andre Kaufmann <akfmn...@t-online.de> wrote:

> JoshuaMaur...@gmail.com wrote:
> > [...]
> > This is the way they used to be implemented. For example, Visual
> > Studios 2003 implements it that way.
>
> > On any good compiler, during normal course of operation, there is
>
> Hm, implies the previous compiler isn't quite good ?

Yes.
Rhetorical question: should we not use templates because they weren't
fully supported in some old compilers?

> By the way this compiler eventually had to track non linear control flow
> too, caused by raised structured exceptions (which are not standard C++).

If true, a severe flaw in the VS 2003 compiler and its language
extensions IMO.

> > absolutely no additional work. When an exception is thrown, it goes to
> > a global lookup table based on the current program counter value. This
> > table is built at compile time. Each entry points to a block of code
>
> Can you really map each line of code to a static lookup table, without
> keeping track which object has been constructed or not ?
>
> One (lame) example:
>
> void foo()
> {
> if (a) goto v2;
> v1:
> object a1;
> v2:
> object a2;
> v3:
> }
>
> How does (such a) the compiler know at v3: if object a1() has been
> constructed or not ?

First, let me rephrase your code so it actually compiles.

class object {};
void foo(bool a)
{ if (a) goto v2;


v1: object a1;
v2: object a2;

v3: ;
}

The previous will compile with comeau.
http://www.comeaucomputing.com/tryitout/

Let's take a look at an example where skipping the object declaration
matters:

class object { public: ~object(); };
void foo(bool a)
{ if (a) goto v2;


v1: object a1;
v2: object a2;

v3: ;
}

The previous will NOT compile, nor will it compile if you declare a
constructor or declare a non-POD member. Your goto example is actually
invalid, as it cannot happen in conforming code. You are not allowed
to skip non-POD object definitions in switch statements and with gotos
for this exact reason (and possibly others).

You absolutely can map the current program counter to which
(automatic, aka stack) objects have been successfully constructed.

Now, I presume all compilers do array initialization in a loop, so
array initialization would require a little extra work as the
exception handler would have to have access to which elements in the
array have been constructed, but this is solvable.

It is my impression that all other problems are also solvable.

> > to properly tear down the currently constructed portion of the stack
> > frame, then pass control to the next compiler-made exception handler
> > of the containing stack frame, or to a user's catch block.
>
> > Now, arguably, it won't result in terribly faster code in most uses.
> > We're just talking about a couple less if statements, but that's a
> > couple less if statements on the normal execution path than what we
> > had before. Less actual machine instructions run, and higher
> > instruction cache hit rates.
>
> Well, depends on the overall code size.

See my last comment in this post.

> Which is the compiler, which has
> that well optimized, table based, exception handling ? I would like to
> have a look at the generated assembly code.

Honestly, I don't know which for sure offhand. Some minimal testing I
did awhile ago showed no measurable exception overhead on VS 2005
whereas the same tests showed very large exception overhead on VS
2003.

> My experience is that my assumptions about the speed of code haven't
> been that accurate today as they have been before - due to multiple
> cores, parallel code execution, prefetching - new optimizing strategies
> like profile guided optimization etc.

I give you the code bloat might make the size of the executable on
disk unacceptable, but it won't affect runtime performance in any
significant way if correctly done (assuming virtual memory).

The exception code will be off by itself, meaning with a virtual
memory setup, nearly all of the time the exception handling code will
be paged out to disk. We pay little no cost to have this exception
handling code around if we don't use it.

Now, I agree that with all of the crazy awesome optimizations done
under the hood, it's hard to gauge hand coding hacks would help and by
how much. However, you would have to work pretty hard to contrive an
example where having the exact same instructions in the exact same
order except with some removed would hurt runtime performance.

(Note that this does assume that exceptions are the rare case. They
are intended as the rare case. Using them as "normal" returns will
greatly hurt runtime performance.)

Andrei Alexandrescu

unread,
Sep 23, 2008, 9:07:11 PM9/23/08
to

I think that design would work too. But I don't like that it allows
dangling pointers.

Andrei

--

Andrei Alexandrescu

unread,
Sep 23, 2008, 9:13:57 PM9/23/08
to
Dave Harris wrote:
> SeeWebsit...@erdani.org (Andrei Alexandrescu) wrote (abridged):
>> Au contraire, the fact that zombie state is the default-constructed
>> state makes it very easy to both attain and check. It only requires
>> careful writing of the destructor (it would be nicer still if the
>> compiler planted the appropriate code itself). Also, since all
>> member functions must check for the default-constructed state to
>> start with, they won't have any trouble in dealing with zombie
>> objects.
> It sounds to me as though that approach permits resurrection. If default
> construction produces a zombie, and first use initialises the object
> properly, and destruction puts the object back into its default
> constructed state, then a use after destruction would reinitialise it.
>
> This strikes me as potentially bad. At best it conceals bugs. At worst
> the resurrected object would not get destroyed a second time, and would
> leak resources and have other problems (eg deadlocks due to locks not
> getting released).

That's a great point, I'm glad you brought it up. This is not the design
I have in mind, so please let me explain mine on an example.

Consider a File type that provides the usual operations. Then say File
defines various iterators for input, output, parsing, the works. They
all "weakly" refer to the File. When File's destructor gets called, the
underlying file handle is closed, thus freeing the resource. The
iterators remain in a "defined but inoperable" state; any attempt to do
I/O through them will reproducibly throw an exception. But the File
object does not disappear! It only zeroes its internal file handle, such
that whenever an iterator tries to use it the closed state can be detected.

Can the file be reopened post destruction? Not via an iterator because
iterators can't manipulate the File. Could someone who has some pointer
to the original File open it again? Yes, but acquiring a resource must
be accompanied by an understanding of the ownership relationship in
vigor for that File. There could be none (the File was already
disowned), in which case the code that reopened it should acquire its
ownership. Not doing so would be a design mistake that indeed may lead
to problems. My point is that the resulting landscape makes it easier to
create correct designs. Allegedly :o).

> In order to prevent resurrection the object needs to know the difference
> between a new default-constructed object and a destroyed one. Code which
> handles one case correctly won't necessarily handle both.

That's a good point. You have convinced me that at least some objects
would need a way to prevent resource re-acquisition after the destructor
was called. Alternatively, a resource wrapper's interface could be split
in two, one that acquires/release the resource, and one that uses the
resource but can't control its lifetime.

This is a very fertile subject. As an aside, I'm a bit surprised that
most people spend energy solely on rehashing the known problems with GC
+ destructors. Yes, it is understood there are problems. The more
interesting and challenging task is to define a system that does work.
Clearly it can't work exactly like today because the tradeoff space is
very different, but let's look for something that does work and presents
another, hopefully better but still different, tradeoff proposition.

> Have you thought about the pImpl idiom? Any object can potentially be
> implemented as a pImpl, in which case it needs memory for the Impl
> whether or not it needs other resources.

> [...]


> Is this what you mean by good style? It strikes me as over-complex.

At this point I am not convinced whether all, or most, objects need to
enforce that meaningful use post-destruction is impossible. I'll
certainly need to think of it.

> I especially dislike the need to use the "mutable" keyword. I'd be
> tempted instead by code like:
>
> int Wrapper::simpleAccessor() const {
> return pImpl ? pImpl->simpleAccessor() : 0;
> }
>
> But this is a bit ad hoc and error-prone. (It is also an example of
> checking for the default-constructed state but not for the destroyed
> state.)

Well that's more of a problem with mutable. C++ hasn't gotten lazy
initialization quite right, and mutable is a bad palliative.

> If instead we strength the class invariant by saying the Impl is always
> allocated, the code gets much simpler and cleaner.

I agree in this case. But then again: in discussing the disadvantages of
an approach, don't forget the advantages.


Andrei

Andrei Alexandrescu

unread,
Sep 23, 2008, 9:12:13 PM9/23/08
to
Marsh Ray wrote:
> On Sep 20, 8:47 pm, Andrei Alexandrescu
> <SeeWebsiteForEm...@erdani.org> wrote:
>> Heck, if deque's constructor attempts to allocate memory (and
>> consequently throw), you can't even use the swap trick to ensure you
>> have completely emptied a deque.
>
> Only if your code is depending on this not throwing, which is a small
> minority of what I write in recent years.
>
>> So picture the irony: _emptying_ a
>> deque via the swap trick could _throw_. This is a correctness issue that
>> I guess simply rules the swap trick out as a method of emptying a
>> container in C++03.
>
> I agree that it's ironic, but don't see how it's a general correctness
> issue.

It's a correctness issue insofar as post-exception your container still
contains stuff it shouldn't. It may even be a general correctness issue
when thought from the perspective that voluntarily shrinking program
state size should not fail.

> I gave up trying to track what throw and what doesn't, and have been
> much happier for it. I assume nearly anything could throw (or later be
> modified to), and tend to only try/catch around calls from C code,
> destructors, and when there's the need to retry an operation.

That sounds good. However, if you want to ensure correctness of
multi-step operations, there must be certain steps that never throw. I
think that steps that relocate objects in memory or that swap objects
should not throw. With that guarantee in place, a good deal of solid
code can be written with relative ease.


Andrei

Nevin :-] Liber

unread,
Sep 23, 2008, 9:11:35 PM9/23/08
to
In article <87d4iwg...@mcbain.luannocracy.com>,
David Abrahams <da...@boostpro.com> wrote:

> on Sat Sep 20 2008, > Sorry, I'm not getting much of a picture. Your swap trick looks like:


>
> deque<int>().swap(d);
>
> Plain as day you are constructing an object whose constructor may throw
> an exception. Personally I'm more fond of
>
> d.clear();
>
> for that purpose.

Sure, if you know it is a deque. How do you do it generically for, say,
any C++03 container? One would have to write code like:

c.clear(); // release external resources
try { C().swap(c); } catch(...) {} // minimize internal resources


Seems a bit clunky...

--
Nevin ":-)" Liber <mailto:ne...@eviloverlord.com> 773 961-1620

Andrei Alexandrescu

unread,
Sep 23, 2008, 9:14:11 PM9/23/08
to
David Abrahams wrote:
> on Sun Sep 21 2008, Andrei Alexandrescu <SeeWebsiteForEmail-AT-erdani.org> wrote:
>
>> Also, if you sorted a vector<deque<int> > and wanted to swap the pivot
>> into a temporary, is it reasonable for that temporary's non-argument
>> initialization to throw?
>
> Yeah, I think it's reasonable. It's worth noting that my standard
> library implementation never needs to default-construct a pivot.

I gave Hoare's partition as a simple example. There are various
algorithms that do need additional O(log N) or even O(N) memory, such as
stable_sort or inplace_merge. STL has the temporary data manipulation
primitives get/return_temporary_buffer, uninitialized_copy,
uninitialized_fill, and uninitialized_fill_n. They are a bit awkward,
but they illustrate a clear need.

> I'm guessing you want to get a nonthrowing sort based on nonthrowing
> move and swap operations?

Yah. In general, again, I think a good programming system should never
fail to relocate objects. This is a pretty fundamental principle to me.

> Of course, if you believe that "all types are Regular," it isn't even a
> question: default construction, move, and swap are all nonthrowing in
> that case.

I don't believe that all types should be regular, but I do think that a
non-throwing default constructor has certain advantages.


Andrei

--

Andrei Alexandrescu

unread,
Sep 23, 2008, 9:15:27 PM9/23/08
to
David Abrahams wrote:
> on Fri Sep 19 2008, Andrei Alexandrescu <SeeWebsiteForEmail-AT-erdani.org> wrote:
>
>> I asked the question because I'm coming from a slightly unusual angle.
>> I've been using a garbage-collected environment for a while. When a GC
>> is in the equation *together* with deterministic destruction,
>> Interesting Things(TM) happen. One of them is that it's a great idea to
>> separate teardown (destruction) from deallocation. For example:
>>
>> Foo * p = new Foo(...);
>> ...
>> delete p;
>>
>> In a GC environment, it makes a lot of sense if the delete statement
>> only invokes Foo's destructor against *p and then... leaves the object
>> alone in a "valid but non-resource-consuming" state.
>
> E.g. all bits zero?

The object defines that state. As a simplification, I suggested that
parameterless initialization would be that state. But for that to work,
parameterless initialization should not throw.

>> Such an approach is not foreign to many on this group; there's been
>> many a discussion about what was descriptively called "zombie" object
>> states.
>
> To accomodate "zombie" states you may need to expand the definition of
> what constitute "valid" states for the object or the ways zombie-ness
> can be reached, weakening the class' invariants.

Again, that only discusses the empty half of the glass. The full part is
that we get rid of dangling pointers. Plus, the way I was thinking of
it, the zombie state would be actually the default-constructed state,
which in many cases does not introduce a new state and does not weaken
invariants.

>> The advantage of separating teardown from deallocation is that there
>> are no more dangling pointers,
>
> Not so fast. If you're willing to define a pointer to a zombie as
> non-dangling, then yeah, OK. But a pointer to a zombie is distinctly
> less safe to use than a pointer to a regular object.

I am willing to define a pointer to a zombie as non-dangling. The zombie
pointer will not have its resource released, and that indeed is a
potential danger.

> You also have to be aware of the limits of the guarantees you can get
> from that. Sure, the object's immediate bits do not get replaced by
> those of some other live object until all the pointers to it are gone
> and GC comes along to sweep up the memory. But you can still walk off
> either edge of the object and read/write.
>
>> and consequently even incorrect programs could achieve predictable,
>> reproducible, and consequently debuggable behavior.
>
> Provided they eschew pointer arithmetic, yeah. There are probably other
> caveats I haven't thought of.

Ah, that brings the discussion on why built-in arrays are superior to
built-in pointers as the sole memory manipulation primitive. I'll save
that for a different time.

> If you are going to use GC in this way, you need to decide whether you
> want it to be part of the language/system definition or simply an
> expression of undefined behavior you can choose (e.g. for your debug
> builds). I strongly favor the latter, for several reasons:
>
> 1. I don't want to be forced to accept the performance cost of these
> checks every time a pointer is dereferenced.

I am not advocating that.

> 2. If the compiler is generating the checks, I don't want to be forced
> to accept the storage cost of representing a "torn-down" state so
> that it can be detected (you need one more bit for that in general,
> and I doubt the compiler can figure out how to hide that bit
> reliably in general).

I am not advocating that. I'm letting objects choose the best way to
define their interfaces. All I'm offering is a simple framework in which
resource lifetime is distinct from object lifetime.

> 3. If the user is writing the checks, I don't want to be forced to
> accept the programming and maintainance cost of representing and
> detecting the zombie state throughout my code. It amounts to
> defensive programming, and IMO it's much easier to write correct code
> without such checking in the way.

Since we already expend effort in ensuring there's no dangling pointers,
there's no extra effort needed.

> 4. You need to choose a defined behavior when the checks fail. I
> suppose aborting is OK, but most language designers are inclined to
> throw exceptions from these operations, a choice which is fraught
> with peril for reason-ability and correctness
> (http://groups.google.com/group/comp.lang.c++.moderated/msg/00b0361b15ae2d1e).
>
> Finally, if you choose to throw, then in order to make the default
> ctor nonthrowing you've just made all the other operations
> potentially throwing. That's a very bad tradeoff.

I don't need to choose a defined behavior when the checks fail, nor
there are any checks to start with. I think you attribute more
complexity to my scheme.

> So, IMO, GC makes good sense as a debugging tool for C++, but not as a
> core language feature.
>
>> That way you get to use GC for what it's best at (recycling memory)
>> and deterministic teardown for what it's best at (timely release of
>> resources). In fact I'd go as far as saying that I think separating
>> teardown from deallocation an essential ingredient in successfully
>> marrying deterministic teardown with garbage collection.
>
> I think it's known and well-accepted by now that GC finalizers cannot in
> general call destructors, which basically leads one directly to the same
> conclusion.
>
>> Once we accept the reality of "destroyed but not deallocated" objects,
>> we need to define that state appropriately.
>
> Not necessarily ;-). Leaving certain things undefined in the language
> can have advantages, not least that the implementation is free to be
> efficient rather than slow and "checked."
>
>> That state claims no more resources than the bits of Foo alone, and
>> must be detected by all member functions of the object such that they
>> have defined behavior for that state.
>
> You're scaring me again. Sounds.... slooooow.

Most objects only use memory-only resources, so only a minority would
ever have a destructor. Those that need one usually manipulate resources
that are (almost by definition) expensive enough to manipulate to dwarf
any in-core overhead associated with managing resource lifetime.

>> d) Non-throwing default constructors occasionally facilitate writing
>> algorithms. For example, in Hoare's partition it's convenient to store
>> the pivot in a local temporary. The temporary is default-constructed and
>> then only swapped around. In other words, non-throwing default
>> constructors allow writing "conservative" algorithms on collections that
>> do not create any new value, but do use temporary memory to shuffle
>> values around.
>
> Yes, it can be convenient, and aside from the fact that uninitialized
> built-ins are also zombies ("extending the semantics of C to
> user-defined types"), I understand that to be Stepanov's reason for
> insisting on nonthrowing default-constructibility in Regular Types.

Again, my zombie isn't scary-looking. It's a default-initialized object.

> However, default-constructibility is not a *huge* convenience in any
> algorithm implementation I've seen, especially not partition, and I
> value the correctness guarantees I can get from the compiler over this
> convenience.

Try stable_sort.

But this doesn't sum anything before it. You mostly mentioned efficiency
concerns throughout, so this "reasoning costs" now comes as a surprise.

> * I fear that such a system will fail miserably to fulfill people's
> expectations for what a GC'd language can provide. If we're lucky
> that would lead to almost nobody taking advantage of the system. If
> we're unlucky, people will write code as though the language can
> fulfill their expectations, when really it can't. Think: exception
> specifications.

Exception specifications are bad anyway, aren't they :o).


Andrei

--

David Abrahams

unread,
Sep 24, 2008, 9:54:15 AM9/24/08
to

on Tue Sep 23 2008, Andrei Alexandrescu <SeeWebsiteForEmail-AT-erdani.org>
wrote:

> David Abrahams wrote:
>> It's worth noting that my standard library implementation never needs
>> to default-construct a pivot.
>
> I gave Hoare's partition as a simple example. There are various
> algorithms that do need additional O(log N) or even O(N) memory, such as
> stable_sort or inplace_merge.

Yes, I know. None needs default construction, so I still don't see the
relevance.

>> Of course, if you believe that "all types are Regular," it isn't even a
>> question: default construction, move, and swap are all nonthrowing in
>> that case.
>
> I don't believe that all types should be regular, but I do think that a
> non-throwing default constructor has certain advantages.

Yeah, some. But if you're going to go as far as saying it's "bad style"
you may as well go all the way to "all types are regular," IMO.

--
Dave Abrahams
BoostPro Computing
http://www.boostpro.com

[ See http://www.gotw.ca/resources/clcm.htm for info about ]

Mathias Gaunard

unread,
Sep 24, 2008, 10:58:59 AM9/24/08
to
On 23 sep, 14:28, Andrei Alexandrescu <SeeWebsiteForEm...@erdani.org>
wrote:

> That's pretty much why I dislike the term zombie for a terminated
> object. To me that object should be eminently usable, just that it is in
> an empty state where it consumes no resources beyond its own bits.

Several proprieties of the object will only be available when the
object is not empty. Therefore you will have to generate errors (most
likely asserts, but some like to use exceptions) if some code attempt
to access the resource that doesn't exist anymore.
On the other hand, not allowing that empty state gives you the
invariant that no such error can occur.

Your empty states may be better than dangling pointers, but not much
better, since they really are like null pointers.

David Abrahams

unread,
Sep 24, 2008, 10:59:00 AM9/24/08
to

on Tue Sep 23 2008, Andrei Alexandrescu <SeeWebsiteForEmail-AT-erdani.org>
wrote:

>> 3. If the user is writing the checks, I don't want to be forced to


>> accept the programming and maintainance cost of representing and
>> detecting the zombie state throughout my code. It amounts to
>> defensive programming, and IMO it's much easier to write correct code
>> without such checking in the way.
>
> Since we already expend effort in ensuring there's no dangling
> pointers,

We do? I'm sure my programs have lots of dangling pointers.

> there's no extra effort needed.

I'm sorry, but I don't see how you reach that conclusion. If the
user needs to check to make sure the object is not-torn-down then it
takes effort.

>> 4. You need to choose a defined behavior when the checks fail. I
>> suppose aborting is OK, but most language designers are inclined to
>> throw exceptions from these operations, a choice which is fraught
>> with peril for reason-ability and correctness
>>
(http://groups.google.com/group/comp.lang.c++.moderated/msg/00b0361b15ae2d1e
).>
>> Finally, if you choose to throw, then in order to make the default
>> ctor nonthrowing you've just made all the other operations
>> potentially throwing. That's a very bad tradeoff.
>
> I don't need to choose a defined behavior when the checks fail, nor
> there are any checks to start with. I think you attribute more
> complexity to my scheme.

I'm sorry, you've made several statements in this thread, like "must be
detected by all member functions of the object" below, that strongly
implied (to me) that you meant there to be checking for the empty state,
either by the user or by the compiler. If that's not what you meant,
please spell out your scheme clearly so that I can appreciate it in all
its non-complexity ;-)

>>> That state claims no more resources than the bits of Foo alone, and
>>> must be detected by all member functions of the object such that they
>>> have defined behavior for that state.
>>
>> You're scaring me again. Sounds.... slooooow.
>
> Most objects only use memory-only resources, so only a minority would
> ever have a destructor.

Yes.

> Those that need one usually manipulate resources that are (almost by
> definition) expensive enough to manipulate to dwarf any in-core
> overhead associated with managing resource lifetime.

I don't see what a resource not being memory has to do with the cost of
managing it. It's entirely possible that the OS makes these resources
very efficient.

I also don't see what a resource not being memory has to do with the
cost of checking in all member functions to see whether it's been
allocated. But then, you say above that you're not suggesting any
checking, so... I'm now scared *and* confused ;-)

>>> d) Non-throwing default constructors occasionally facilitate writing
>>> algorithms. For example, in Hoare's partition it's convenient to store
>>> the pivot in a local temporary. The temporary is default-constructed and
>>> then only swapped around. In other words, non-throwing default
>>> constructors allow writing "conservative" algorithms on collections that
>>> do not create any new value, but do use temporary memory to shuffle
>>> values around.
>>
>> Yes, it can be convenient, and aside from the fact that uninitialized
>> built-ins are also zombies ("extending the semantics of C to
>> user-defined types"), I understand that to be Stepanov's reason for
>> insisting on nonthrowing default-constructibility in Regular Types.
>
> Again, my zombie isn't scary-looking. It's a default-initialized object.

I'm not scared by your zombie. I'm scared by the
programmer/grok-ability cost of including that state in all class
invariants and making it available from the moment of the object's
construction (as opposed to just after a move, for example).

>> However, default-constructibility is not a *huge* convenience in any
>> algorithm implementation I've seen, especially not partition, and I
>> value the correctness guarantees I can get from the compiler over this
>> convenience.
>
> Try stable_sort.

The standard library doesn't require default constructability for its
implementation. Furthermore, I daresay stable_sort is complicated
enough by nature (it uses nearly all the other algorithms in the STL)
that I can't imagine default constructability making a huge impact.
Where would you use it?

You must have overlooked several things I've written in this thread. I
mentioned it earlier in the very message you are replying to:

4. You need to choose a defined behavior when the checks fail. I
suppose aborting is OK, but most language designers are inclined to
throw exceptions from these operations, a choice which is fraught
with peril for reason-ability and correctness

(http://groups.google.com/group/comp.lang.c++.moderated/msg/00b0361b15ae2d1e
).

Finally, if you choose to throw, then in order to make the default
ctor nonthrowing you've just made all the other operations
potentially throwing. That's a very bad tradeoff.

incidentally that last part is not about efficiency. Also the issue of
not having developed a reliable programming model that ensures things
that must be cleaned up actually do get cleaned up and yet gives people
what they expect from GC is strongly related to our ability to reason
about the code.

>> * I fear that such a system will fail miserably to fulfill people's
>> expectations for what a GC'd language can provide. If we're lucky
>> that would lead to almost nobody taking advantage of the system. If
>> we're unlucky, people will write code as though the language can
>> fulfill their expectations, when really it can't. Think: exception
>> specifications.
>
> Exception specifications are bad anyway, aren't they :o).

Not sure what you're driving at. My point was that one of the major
problems with exception specifications is also a problem for GC in C++:
people want to believe the feature is doing something for them that it
can't do.

--
Dave Abrahams
BoostPro Computing
http://www.boostpro.com

[ See http://www.gotw.ca/resources/clcm.htm for info about ]

Nevin :-] Liber

unread,
Sep 24, 2008, 11:07:15 AM9/24/08
to
In article <K7Mr0...@beaver.cs.washington.edu>,
Andrei Alexandrescu <SeeWebsit...@erdani.org> wrote:

> But the File
> object does not disappear! It only zeroes its internal file handle, such
> that whenever an iterator tries to use it the closed state can be
detected.

In this system, whose responsibility is it to put in such a state?
Should the destructor do it, or would the compiler insert code after the
destructor to "placement new" a default constructed object in its place?

The reason I ask is if I have a simple object, such as

struct SomeFunctor
{
SomeFunctor(State* state = 0) : state(State) {}
State* state;

void operator()(Var& var) { /* Do something */ }
};

it should not require the author to write a destructor, force the author
to use some kind of smart pointer to NULL it, etc., just to make the GC
system happy. Simple things need to remain simple.

--
Nevin ":-)" Liber <mailto:ne...@eviloverlord.com> 773 961-1620

[ See http://www.gotw.ca/resources/clcm.htm for info about ]

Nevin :-] Liber

unread,
Sep 24, 2008, 11:07:34 AM9/24/08
to
In article <gavlks$qp1$1...@news.datemas.de>,
Gerhard Menzl <clcppm...@this.is.invalid> wrote:

> As soon as you want to use
> Something, you will trigger the delayed operation, and then you have to
> be prepared for exceptions anyway.

But the place where I use the deque is different than the place where I
declare it.

It doesn't take an expert to expect an insert to possibly throw an
exception. It does take an expert to expect that an empty deque might
throw an exception.

Before I told you, did you honestly expect that an empty deque, unlike
every other STL container, could throw an exception?

This also has performance implications. If one has a FIFO data
structure that might be empty most of the time, std::list can
unintuitively be a better choice.

This is why people think of C++ as an expert only language...

Eugene Gershnik

unread,
Sep 24, 2008, 11:06:59 AM9/24/08
to
Andrei Alexandrescu wrote:
> This is a very fertile subject. As an aside, I'm a bit surprised that
> most people spend energy solely on rehashing the known problems with GC
> + destructors. Yes, it is understood there are problems. The more
> interesting and challenging task is to define a system that does work.
> Clearly it can't work exactly like today because the tradeoff space is
> very different, but let's look for something that does work and presents
> another, hopefully better but still different, tradeoff proposition.

Well here are 3 things that can be done in order to make destructors
work well with GC

1) Make the language zero initialize[1] object bits prior to running
any constructor. This is your zombie state (as opposed to the one
after default constructor)
2) Make the language wipe the object to the same zero-initialized
state after desturctor finishes. This frees programmer to write
destructors as he always had done and ensures that a single well
defined zombie state is reached automatically.
3) Make the language supply a function is_zombie() that returns true
if the object is in that same zero-initialized state. This, again,
prevents the programmer from making a mistake in writing one manually.
In general the object itself doesn't need to call this function, only
its users are if they have reason to believe that an object might be a
zombie.

[1] I use this term loosely. What it means is that integers are set to
0, pointers and pointers used to implement references[2] to NULL,
bools to false, floats to 0.0 etc. and so recursively for non-
primitive subobjects.
[2] Having NULL references is ok since they can never be observed in
correct code.

With these changes you can write reasonably safe code without relying
on manual discipline to maintain zombie state. Even better the class
implementation would usually look exactly like it does today with no
additional state machines. Only users of a class and only in the few
contexts where they might encounter a zombie object (like lock free
algorithms) will need to do anything special.
Also note that these changes are highly backward compatible. No
standard-conforming current C++ code should be affected by them (other
than in speed) or be able to observe them.

--
Eugene

Marsh Ray

unread,
Sep 24, 2008, 11:06:45 AM9/24/08
to
On Sep 23, 8:12 pm, Andrei Alexandrescu
<SeeWebsiteForEm...@erdani.org> wrote:
> [] However, if you want to ensure correctness of

> multi-step operations, there must be certain steps that never throw.

There is another option: An exception from any step can be handled as
long as all preceding steps are reversible. From one point of view,
this is perhaps just pushing the problem around (since the unwinding
operations are all now things that can't fail), and some missiles
obviously cannot be unlaunched. However, I'd wager that in the
majority of cases it's easier for us (really the user of our generic
function) to guarantee that we can reestablish the initial state than
that we'll achieve every postcondition along the way. Still waiting
for that library container that needs to launch missiles during
reallocation.

IOW, there's another guarantee beyond strong: an operation for which
the strong guarantee holds in the forward direction and is nothrow in
the reverse. This could also be thought of in terms of the operation
still giving the strong guarantee even if it took an extra (strong
guarantee) functor parameter to invoke just before returning normally.

I wonder if the universe of rollback-able operations is usefully
larger than that of the nothrows.

> I think that steps that relocate objects in memory or that swap objects
> should not throw.

For example, even if swap<T>(...) could fail because T() needs to grab
a bit of storage, if it succeeded in the forward direction, those
exact same resources should be available when the same swap<T>...T()
gets used as part of the unwinding process. Well, in theory
anyway. :-)

> With that guarantee in place, a good deal of solid
> code can be written with relative ease.

Software and hardware transactional memory seem pretty hot these days.
I wonder if some C++ 0x+n won't be needing to provide something in
that area like we're seeing currently with threading. Probably
something interesting could be done in library space here.

- Marsh

James Hopkin

unread,
Sep 24, 2008, 11:09:02 AM9/24/08
to
On Sep 23, 1:29 am, Andrei Alexandrescu
<SeeWebsiteForEm...@erdani.org> wrote:
> Mathias Gaunard wrote:
> > All default constructors of containers are not required to be nothrow.
>
> > Electronic Arts has asked the standard to change that, however,
> > showing their own EASTL as an improvement upon the STL.
>
> I'm of the same opinion as them.
>
> Andrei
>

Me too, but I don't think it implies much about whether throwing
default constructors *in general* are problematic.

I think extrapolating too much from containers can be helpful but
potentially misleading. Over-generalising in the other direction:
containers are more malleable and have simpler invariants than a lot
of types.

James

Mathias Gaunard

unread,
Sep 24, 2008, 12:58:57 PM9/24/08
to
On 23 sep, 02:34, Andre Kaufmann <akfmn...@t-online.de> wrote:

> I only stated, that it's a pitfall - even if helper objects will reduce
> the chance to run into such a problem, since not every developer likes
> to use an extra helper object, just to wrap 2 function calls. I try to
> use helper object, but sometimes I don't too.

That's why I also showed the try/catch(...) way of dealing with it,
for people that do not want to deal with extra objects.

Dave Harris

unread,
Sep 24, 2008, 12:59:02 PM9/24/08
to
SeeWebsit...@erdani.org (Andrei Alexandrescu) wrote (abridged):
> Consider a File type that provides the usual operations.

Certainly there are situations where the approach is painless and then
it's the natural thing to do. Vector is one example, and File is another.
File in particular because you expect to have to tell it the name of the
file you want to use, so it makes sense to have an explicit "open" step
before using the other operations. I agree that where it is easy it is
often good idea to postpone acquiring resources until they are actually
needed.

The tricky situations are where the object doesn't need any arguments to
be usable, because then it feels unnatural to have an explicit "open"
step, and without that we get a can of worms with resurrection,
unexpected throws, efficiency hits etc. If we want a general rule, then
we have to think about the difficult cases.


> When File's destructor gets called, the underlying file handle

> is closed, thus freeing the resource. [...]
>
> Can the file be reopened post destruction? [...] Yes [...]

When you say "destructor", do you mean "dispose"?

In standard C++, the destructor changes the type of the object, from
derived class to base class and then to raw memory. The vtable pointer
gets reset.

struct BaseFile {
virtual bool open( const char *name );
};
struct File {
virtual bool open( const char *name );
};

It sounds like after "File's destructor gets called", you still expect a
call to open() to invoke File::open() rather than BaseFile::open(). If so,
then I'd rather not use the terminology of destruction, destructors etc.
The word "dispose" better reflects the meaning.

I don't think the keyword "delete" should be changed to mean dispose in a
GC environment. That's too scary a change for me.


> This is a very fertile subject. As an aside, I'm a bit surprised
> that most people spend energy solely on rehashing the known
> problems with GC + destructors. Yes, it is understood there are
> problems. The more interesting and challenging task is to define
> a system that does work.

We should be no worse off if instead of the object being destroyed, it is
left in a zombie state with well-defined behaviour at the language level.
This is an opportunity, and I agree it warrants some thought as to how
best to exploit it.

However, although resurrection can make sense, in most cases if you
dispose an object it will mean you are finished with it, and using it
after would be a bug. So I see GC/disposal as providing opportunities to
better detect bugs, and whether to throw an exception or assert depends
on how one thinks bugs should be dealt with: whether one believes in
defensive programming or design by contract.

(That's also my conclusion about finalisers, although for different
reasons. I don't believe there is much useful that can be done with a
finaliser except to detect the mistake of failing to dispose the object.)

So to me the default constructed state is the opposite of the disposed
state. One is the start of the object's lifetime, and the other is
effectively the end. And to tie these together, and say that the default
constructed state should be the same as the disposed state, is a bit
unnatural. Much of what you write seems to be tantamount to saying
defensive programming wins.


> But then again: in discussing the disadvantages of
> an approach, don't forget the advantages.

It sounds like the main advantage you seek is for default construction
not to throw. But in C++, construction is exactly when I do expect the
object to throw, because of RAII. (And because they are often created on
the heap, and operator new() can throw.) I am more surprised if
subsequent, apparently innocuous methods throw (because they are secretly
acquiring resources that should have been got in the constructor).

-- Dave Harris, Nottingham, UK.

David Abrahams

unread,
Sep 24, 2008, 12:58:59 PM9/24/08
to

on Tue Sep 23 2008, Andrei Alexandrescu <SeeWebsiteForEmail-AT-erdani.org> wrote:

> That's pretty much why I dislike the term zombie for a terminated
> object. To me that object should be eminently usable, just that it is in
> an empty state where it consumes no resources beyond its own bits.
>
> In that view of the world, objects remain infinitely-lived as they've
> always been in a GC system. On top of that, destructors just make sure
> that additional resources that objects occasionally acquire are released
> at specific boundaries. To be honest, the more I think about that model,
> the more I like it. :o)

So IIUC, in this model:

* you still need to remember to tear everything down before you leak it

* torn-down objects are just as good as any others

* you don't get any help finding places where you use torn-down
objects, because it's totally legal to do so

* GC is buying you some type safety

Have I got that right?

Do you place objects on the stack, or is it a world of GC'd pointers?

--
Dave Abrahams
BoostPro Computing
http://www.boostpro.com

[ See http://www.gotw.ca/resources/clcm.htm for info about ]

Francis Glassborow

unread,
Sep 24, 2008, 12:58:59 PM9/24/08
to
Andre Kaufmann wrote:
> Joshua...@gmail.com wrote:
>> [...]
>> This is the way they used to be implemented. For example, Visual
>> Studios 2003 implements it that way.
>>
>> On any good compiler, during normal course of operation, there is
>
> Hm, implies the previous compiler isn't quite good ?
> By the way this compiler eventually had to track non linear control flow
> too, caused by raised structured exceptions (which are not standard C++).
>
>> absolutely no additional work. When an exception is thrown, it goes to
>> a global lookup table based on the current program counter value. This
>> table is built at compile time. Each entry points to a block of code
>
> Can you really map each line of code to a static lookup table, without
> keeping track which object has been constructed or not ?
>
> One (lame) example:
>
> void foo()
> {
>
> if (a) goto v2;
> v1:
> object a1;
> v2:
> object a2;
> v3:
> }
>
> How does (such a) the compiler know at v3: if object a1() has been
> constructed or not ?

I thought that code was ill formed because of the jump over and the
construction of a1.

Mathias Gaunard

unread,
Sep 24, 2008, 12:58:58 PM9/24/08
to
On 23 sep, 02:33, Andre Kaufmann <akfmn...@t-online.de> wrote:

> Well, depends on the overall code size. Which is the compiler, which has
> that well optimized, table based, exception handling ? I would like to
> have a look at the generated assembly code.

Virtually all recent compilers but Windows ones follow the Itanium
ABI, since it is the only platform-independent C++ ABI standard.
Windows has its own ABI, so compilers tend to follow it on that
platform.

Andrei Alexandrescu

unread,
Sep 24, 2008, 3:28:58 PM9/24/08
to
Mathias Gaunard wrote:
> On 23 sep, 14:28, Andrei Alexandrescu <SeeWebsiteForEm...@erdani.org>
> wrote:
>
>> That's pretty much why I dislike the term zombie for a terminated
>> object. To me that object should be eminently usable, just that it is in
>> an empty state where it consumes no resources beyond its own bits.
>
> Several proprieties of the object will only be available when the
> object is not empty. Therefore you will have to generate errors (most
> likely asserts, but some like to use exceptions) if some code attempt
> to access the resource that doesn't exist anymore.
> On the other hand, not allowing that empty state gives you the
> invariant that no such error can occur.

Clearly not allowing the empty state gives you a stronger invariant. I
am not contending that. But that approach has a hard time when it comes
about accessing objects that have been destroyed (and therefore did
enter an empty state).

> Your empty states may be better than dangling pointers, but not much
> better, since they really are like null pointers.

I do think that null pointers are progress compared to dangling
pointers. Also, empty states are more than null pointers. They can be
functional and informative the same way an empty vector is more
functional and more informative than a null pointer.


Andrei

Andrei Alexandrescu

unread,
Sep 24, 2008, 3:28:57 PM9/24/08
to
Eugene Gershnik wrote:
> Andrei Alexandrescu wrote:
>> This is a very fertile subject. As an aside, I'm a bit surprised that
>> most people spend energy solely on rehashing the known problems with GC
>> + destructors. Yes, it is understood there are problems. The more
>> interesting and challenging task is to define a system that does work.
>> Clearly it can't work exactly like today because the tradeoff space is
>> very different, but let's look for something that does work and presents
>> another, hopefully better but still different, tradeoff proposition.
>
> Well here are 3 things that can be done in order to make destructors
> work well with GC
>
> 1) Make the language zero initialize[1] object bits prior to running
> any constructor. This is your zombie state (as opposed to the one
> after default constructor)
> 2) Make the language wipe the object to the same zero-initialized
> state after desturctor finishes. This frees programmer to write
> destructors as he always had done and ensures that a single well
> defined zombie state is reached automatically.
> 3) Make the language supply a function is_zombie() that returns true
> if the object is in that same zero-initialized state. This, again,
> prevents the programmer from making a mistake in writing one manually.
> In general the object itself doesn't need to call this function, only
> its users are if they have reason to believe that an object might be a
> zombie.

There's one thing with this approach, that it requires the
zero-initialized state to be a sort of bad state. Consider:

struct Point { unsigned x, y; const char* color; }

In some design a zero-initialized point could mean a black point at the
origin. The problem is that that's also a zombie point, which comes off
as a surprise.

Other than that, this is a pretty good scheme. But I see the above a
major issue.


Andrei

Andrei Alexandrescu

unread,
Sep 24, 2008, 3:28:57 PM9/24/08
to
David Abrahams wrote:
> on Tue Sep 23 2008, Andrei Alexandrescu <SeeWebsiteForEmail-AT-erdani.org>
wrote:
>
>> That's pretty much why I dislike the term zombie for a terminated
>> object. To me that object should be eminently usable, just that it is in
>> an empty state where it consumes no resources beyond its own bits.
>>
>> In that view of the world, objects remain infinitely-lived as they've
>> always been in a GC system. On top of that, destructors just make sure
>> that additional resources that objects occasionally acquire are released
>> at specific boundaries. To be honest, the more I think about that model,
>> the more I like it. :o)
>
> So IIUC, in this model:
>
> * you still need to remember to tear everything down before you leak it

Not if you use scoped ownership as is often the case in C++.

> * torn-down objects are just as good as any others

Yah.

> * you don't get any help finding places where you use torn-down
> objects, because it's totally legal to do so

Yah. You can build support inside the objects themselves when there's a
need for it.

> * GC is buying you some type safety

Yah.

> Have I got that right?

Yah.

> Do you place objects on the stack, or is it a world of GC'd pointers?

Great question. Objects can live on the stack. If the seatbelt is on,
escaping of addresses of locals is statically verboten. But escaping of
addresses of objects indirectly owned by stack locals is allowed.


Andrei

--

Andrei Alexandrescu

unread,
Sep 24, 2008, 3:28:58 PM9/24/08
to
Nevin :-] Liber wrote:
> In article <K7Mr0...@beaver.cs.washington.edu>,
> Andrei Alexandrescu <SeeWebsit...@erdani.org> wrote:
>
>> But the File
>> object does not disappear! It only zeroes its internal file handle, such
>> that whenever an iterator tries to use it the closed state can be
> detected.
>
> In this system, whose responsibility is it to put in such a state?
> Should the destructor do it, or would the compiler insert code after the
> destructor to "placement new" a default constructed object in its place?

Good question. I think the destructor should take care of that. The
problem is that sometimes overwriting is not necessary and therefore
becomes wasted time.

> The reason I ask is if I have a simple object, such as
>
> struct SomeFunctor
> {
> SomeFunctor(State* state = 0) : state(State) {}
> State* state;
>
> void operator()(Var& var) { /* Do something */ }
> };
>
> it should not require the author to write a destructor, force the author
> to use some kind of smart pointer to NULL it, etc., just to make the GC
> system happy. Simple things need to remain simple.

Agreed. Notice that in this cases there's no resource ownership to start
with, so perhaps there is no need to do the automatic overwriting
thingy. That should be done only if the class defines the destructor. I
guess :o).


Andrei

--

David Abrahams

unread,
Sep 24, 2008, 4:22:32 PM9/24/08
to

on Sat Sep 20 2008, brangdon-AT-cix.co.uk (Dave Harris) wrote:

> For me one of the main ways a default constructor is special is that it
> is used for arrays. So the question is whether code like:
> Foo array[100];
>
> should create 100 zombie Foos, or 100 usable ones. The people who don't
> expect code to throw would presumably want zombies, but I can imagine
> situations where having to follow this with a loop:
>
> Foo array[100];
> for (int i = 0; i < 100; ++i)
> array[i].initialise();
>
> would be inelegant. And of course the opposite, eg where an array of N
> items is allocated in advance but only the first M items are in use and
> need to consume resources.
>
> I don't think we can have absolute rules here, only guidelines.

When you change the deterministic destruction contract, you'd darn well
better have a well-considered programming model to account for the
changes. I don't think "guidelines" are sufficient.

--
Dave Abrahams
BoostPro Computing
http://www.boostpro.com

[ See http://www.gotw.ca/resources/clcm.htm for info about ]

David Abrahams

unread,
Sep 24, 2008, 7:37:40 PM9/24/08
to

on Wed Sep 24 2008, Andrei Alexandrescu <SeeWebsiteForEmail-AT-erdani.org> wrote:

> David Abrahams wrote:
>> on Tue Sep 23 2008, Andrei Alexandrescu <SeeWebsiteForEmail-AT-erdani.org>
> wrote:
>>
>>> That's pretty much why I dislike the term zombie for a terminated
>>> object. To me that object should be eminently usable, just that it is in
>>> an empty state where it consumes no resources beyond its own bits.
>>>
>>> In that view of the world, objects remain infinitely-lived as they've
>>> always been in a GC system. On top of that, destructors just make sure
>>> that additional resources that objects occasionally acquire are released
>>> at specific boundaries. To be honest, the more I think about that model,
>>> the more I like it. :o)
>>
>> So IIUC, in this model:
>>
>> * you still need to remember to tear everything down before you leak it
>
> Not if you use scoped ownership as is often the case in C++.

At the top level, if it's not on the stack, you're not leaking anything
in that case. Otherwise, you need something analogous to p->~T() at the
top level.

>> * torn-down objects are just as good as any others
>
> Yah.
>
>> * you don't get any help finding places where you use torn-down
>> objects, because it's totally legal to do so
>
> Yah. You can build support inside the objects themselves when there's
> a need for it.

Hmmm.... so most torn-down objects are "in a good state," but some are
not.

>> * GC is buying you some type safety
>
> Yah.
>
>> Have I got that right?
>
> Yah.
>
>> Do you place objects on the stack, or is it a world of GC'd pointers?
>
> Great question. Objects can live on the stack. If the seatbelt is on,
> escaping of addresses of locals is statically verboten.

Then this is not C++. I might've guessed, but I wish that had been
clear from the start.

> But escaping of addresses of objects indirectly owned by stack locals
> is allowed.

--

Dave Abrahams
BoostPro Computing
http://www.boostpro.com

[ See http://www.gotw.ca/resources/clcm.htm for info about ]

Erik Wikström

unread,
Sep 24, 2008, 7:44:09 PM9/24/08
to
On 2008-09-24 03:15, Andrei Alexandrescu wrote:

> Since we already expend effort in ensuring there's no dangling pointers,
> there's no extra effort needed.

I must ask, since your main motivation for this scheme seems to be to
prevent dangling pointers, does the C++ programming community as a whole
have great problems with dangling pointers? I know that I myself never
had much problems with them, and where I'm currently working I think
most of our pointer-related problems are memory-leaks or uninitialised/
NULL pointers. And we are writing in C, where we do not have the benefit
from constructors/destructors.

--
Erik Wikström

Andrei Alexandrescu

unread,
Sep 24, 2008, 7:41:43 PM9/24/08
to
Dave Harris wrote:
> SeeWebsit...@erdani.org (Andrei Alexandrescu) wrote (abridged):
>> Consider a File type that provides the usual operations.
>
> Certainly there are situations where the approach is painless and then
> it's the natural thing to do. Vector is one example, and File is another.
> File in particular because you expect to have to tell it the name of the
> file you want to use, so it makes sense to have an explicit "open" step
> before using the other operations. I agree that where it is easy it is
> often good idea to postpone acquiring resources until they are actually
> needed.
>
> The tricky situations are where the object doesn't need any arguments to
> be usable, because then it feels unnatural to have an explicit "open"
> step, and without that we get a can of worms with resurrection,
> unexpected throws, efficiency hits etc. If we want a general rule, then
> we have to think about the difficult cases.

I agree. But then if both painful/painless situations are useful, then
it may make sense to classify them in distinct bins. Right now they're
in the same bin.

>> When File's destructor gets called, the underlying file handle
>> is closed, thus freeing the resource. [...]
>>
>> Can the file be reopened post destruction? [...] Yes [...]
>
> When you say "destructor", do you mean "dispose"?

Yah.

> In standard C++, the destructor changes the type of the object, from
> derived class to base class and then to raw memory. The vtable pointer
> gets reset.
>
> struct BaseFile {
> virtual bool open( const char *name );
> };
> struct File {
> virtual bool open( const char *name );
> };
>
> It sounds like after "File's destructor gets called", you still expect a
> call to open() to invoke File::open() rather than BaseFile::open(). If so,
> then I'd rather not use the terminology of destruction, destructors etc.
> The word "dispose" better reflects the meaning.

Good point. The compiler could do some footwork in calling that
nonthrowing default constructor for File right after destruction.

> I don't think the keyword "delete" should be changed to mean dispose in a
> GC environment. That's too scary a change for me.

I understand. I do think that would be a sensible decision for D, it
being a new language built with GC from the get-go.

>> This is a very fertile subject. As an aside, I'm a bit surprised
>> that most people spend energy solely on rehashing the known
>> problems with GC + destructors. Yes, it is understood there are
>> problems. The more interesting and challenging task is to define
>> a system that does work.
>
> We should be no worse off if instead of the object being destroyed, it is
> left in a zombie state with well-defined behaviour at the language level.
> This is an opportunity, and I agree it warrants some thought as to how
> best to exploit it.
>
> However, although resurrection can make sense, in most cases if you
> dispose an object it will mean you are finished with it, and using it
> after would be a bug. So I see GC/disposal as providing opportunities to
> better detect bugs, and whether to throw an exception or assert depends
> on how one thinks bugs should be dealt with: whether one believes in
> defensive programming or design by contract.

I think we all need some mindset adjustment when trying to reconcile GC
with deterministic resource management. I'd be the first to admit that
my mindset is not entirely adjusted. For example zombie states and
resurrection are both regarded with contempt, but I see them more as
"unowned" and "owned" states. It is easy to provide a flag and functions
like "own", "disown", and "is_owned" for an object that needs them. In
that model, the user of a resource could ask whether that resource has
an owner, and releasing it explicitly if it has no owner.

> (That's also my conclusion about finalisers, although for different
> reasons. I don't believe there is much useful that can be done with a
> finaliser except to detect the mistake of failing to dispose the object.)

Yah finalisers are pretty botched.

> So to me the default constructed state is the opposite of the disposed
> state. One is the start of the object's lifetime, and the other is
> effectively the end. And to tie these together, and say that the default
> constructed state should be the same as the disposed state, is a bit
> unnatural. Much of what you write seems to be tantamount to saying
> defensive programming wins.

I don't particularly like defensive programming, but I also wanted to
make a simple proposition that does not add a specific state.

>> But then again: in discussing the disadvantages of
>> an approach, don't forget the advantages.
>
> It sounds like the main advantage you seek is for default construction
> not to throw. But in C++, construction is exactly when I do expect the
> object to throw, because of RAII. (And because they are often created on
> the heap, and operator new() can throw.) I am more surprised if
> subsequent, apparently innocuous methods throw (because they are secretly
> acquiring resources that should have been got in the constructor).

I think things can be worked out even if the default constructor does
throw. (It would require destructors to be able to throw, which is
possible and IMHO needed by C++.) But allow me this conjecture:

1. If a default constructor may throw, it can be presumed that it failed
to acquire proper state.

2. Because of that it can be presume that whatever was needed was in
scarce supply.

3. Then that means that supply should be refunded pronto, otherwise
other objects will have even less of it.

4. Then the post-destructed state should not default-construct the
object because that works straight against the notion of pronto refunding.

So we go back to the idea that post-destroyed state is distinct from all
other states yet valid, which IMHO brings more problems than it solves.


Andrei

Erik Wikström

unread,
Sep 24, 2008, 7:42:57 PM9/24/08
to
On 2008-09-24 17:06, Eugene Gershnik wrote:
> Andrei Alexandrescu wrote:
>> This is a very fertile subject. As an aside, I'm a bit surprised that
>> most people spend energy solely on rehashing the known problems with GC
>> + destructors. Yes, it is understood there are problems. The more
>> interesting and challenging task is to define a system that does work.
>> Clearly it can't work exactly like today because the tradeoff space is
>> very different, but let's look for something that does work and presents
>> another, hopefully better but still different, tradeoff proposition.
>
> Well here are 3 things that can be done in order to make destructors
> work well with GC
>
> 1) Make the language zero initialize[1] object bits prior to running
> any constructor. This is your zombie state (as opposed to the one
> after default constructor)
> 2) Make the language wipe the object to the same zero-initialized
> state after desturctor finishes. This frees programmer to write
> destructors as he always had done and ensures that a single well
> defined zombie state is reached automatically.
> 3) Make the language supply a function is_zombie() that returns true
> if the object is in that same zero-initialized state. This, again,
> prevents the programmer from making a mistake in writing one manually.
> In general the object itself doesn't need to call this function, only
> its users are if they have reason to believe that an object might be a
> zombie.

How do you distinguish a zombie from a valid object which happens to be
all zeroes? Imagine a simple class such as

struct Complex {
double re, im;
};

where all bits of the object being 0 is a valid state. I can see no safe
way of marking an object as zombie without using dedicated memory for it.

--
Erik Wikström

Andrei Alexandrescu

unread,
Sep 24, 2008, 7:41:40 PM9/24/08
to
Marsh Ray wrote:
> On Sep 23, 8:12 pm, Andrei Alexandrescu
> <SeeWebsiteForEm...@erdani.org> wrote:
>> [] However, if you want to ensure correctness of
>> multi-step operations, there must be certain steps that never throw.
>
> There is another option: An exception from any step can be handled as
> long as all preceding steps are reversible. From one point of view,
> this is perhaps just pushing the problem around (since the unwinding
> operations are all now things that can't fail), and some missiles
> obviously cannot be unlaunched. However, I'd wager that in the
> majority of cases it's easier for us (really the user of our generic
> function) to guarantee that we can reestablish the initial state than
> that we'll achieve every postcondition along the way. Still waiting
> for that library container that needs to launch missiles during
> reallocation.
>
> IOW, there's another guarantee beyond strong: an operation for which
> the strong guarantee holds in the forward direction and is nothrow in
> the reverse. This could also be thought of in terms of the operation
> still giving the strong guarantee even if it took an extra (strong
> guarantee) functor parameter to invoke just before returning normally.
>
> I wonder if the universe of rollback-able operations is usefully
> larger than that of the nothrows.

This is a great point. (I've learned a great deal of insights from this
thread, it's great.) I wonder the same too; in my experience it's not,
but then that's not proof. Either an operation does the work on the side
and swap()s it with the original object, or first saves a copy of the
object, operates on the original object, and in case that fails swaps
the old content back into the object. Surprise, surprise, the
requirement that rollback be nothrow is the moral equivalent of the
commit being nothrow.

>> I think that steps that relocate objects in memory or that swap objects
>> should not throw.
>
> For example, even if swap<T>(...) could fail because T() needs to grab
> a bit of storage, if it succeeded in the forward direction, those
> exact same resources should be available when the same swap<T>...T()
> gets used as part of the unwinding process. Well, in theory
> anyway. :-)

I'd be curious to see whether this actually could work.


Andrei

Andrei Alexandrescu

unread,
Sep 24, 2008, 7:41:50 PM9/24/08
to
James Hopkin wrote:
> On Sep 23, 1:29 am, Andrei Alexandrescu
> <SeeWebsiteForEm...@erdani.org> wrote:
>> Mathias Gaunard wrote:
>>> All default constructors of containers are not required to be nothrow.
>>> Electronic Arts has asked the standard to change that, however,
>>> showing their own EASTL as an improvement upon the STL.
>> I'm of the same opinion as them.
>>
>> Andrei
>>
>
> Me too, but I don't think it implies much about whether throwing
> default constructors *in general* are problematic.

I agree. So let me restart. Do you agree that in a GC system, there
should be _some_ constructor that doesn't throw (that being the
constructor called when the object enters the destroyed state)? If we do
agree on that, what remains is to define that constructor.

> I think extrapolating too much from containers can be helpful but
> potentially misleading. Over-generalising in the other direction:
> containers are more malleable and have simpler invariants than a lot
> of types.

I agree with that too.


Andrei

Andrei Alexandrescu

unread,
Sep 25, 2008, 5:48:33 AM9/25/08
to
Erik Wikström wrote:
> On 2008-09-24 03:15, Andrei Alexandrescu wrote:
>
>> Since we already expend effort in ensuring there's no dangling pointers,
>> there's no extra effort needed.
>
> I must ask, since your main motivation for this scheme seems to be to
> prevent dangling pointers, does the C++ programming community as a whole
> have great problems with dangling pointers? I know that I myself never
> had much problems with them, and where I'm currently working I think
> most of our pointer-related problems are memory-leaks or uninitialised/
> NULL pointers. And we are writing in C, where we do not have the benefit
> from constructors/destructors.

If you have too many memory leaks, you have too few dangling pointers.

Andrei :o)


--

Andrei Alexandrescu

unread,
Sep 25, 2008, 5:59:18 AM9/25/08
to

I think there would be a lot of merit in the notion of a safe C++ in
which certain operations are statically rejected. We've gone some of
that way already, with three-argument std::copy raising an increasing
amount of eybrows and with compiler warnings at strcpy, sprintf et al.

Aout the larger discussion, upon more thinking, I was really
hard-pressed to come up with valid designs resuscitating zombie objects.
I think it's better to say that once a destructor has run, it should
mark the resource as just gone. The object remains in there for type
safety, so behavior is reproducible, but should refuse to reallocate
resources because it can't release them. Not very attractive...


Andrei


--

David Abrahams

unread,
Sep 25, 2008, 5:59:05 AM9/25/08
to

on Wed Sep 24 2008, Andrei Alexandrescu <SeeWebsiteForEmail-AT-erdani.org> wrote:

> Either an operation does the work on
> the side and swap()s it with the original object, or first saves a
> copy of the object, operates on the original object, and in case that
> fails swaps the old content back into the object.

Or neither. Take a look at vector<T>::push_back.

The standard library gives the strong guarantee in precisely those cases
where it doesn't have to make a copy/swap sacrifice.

> Surprise, surprise, the requirement that rollback be nothrow is the
> moral equivalent of the commit being nothrow.

I don't understand the point you're trying to make here, but I think
your premise above isn't quite right, so you might want to reconsider in
that light.

--
Dave Abrahams
BoostPro Computing
http://www.boostpro.com

[ See http://www.gotw.ca/resources/clcm.htm for info about ]

Eugene Gershnik

unread,
Sep 25, 2008, 5:52:37 AM9/25/08
to
Andrei Alexandrescu wrote:
>
> There's one thing with this approach, that it requires the
> zero-initialized state to be a sort of bad state.
>

This isn't a big limitation and, in the rare case it might turn out to
be one, there is an easy workaround (see below). Yes is_zombie() will
return true for an object that is all zeroes but why is it bad? Your
code may contain:

if (!is_zombie(p))
delete p;

so for an empty struct Point { unsigned x, y; } you will not call the
destructor but so what? This class doesn't need the destructor at all
to begin with. The only time you need a destructor is if you have a
non-null pointer or, possibly, some kind of non-zero integral "handle"
as a member. And this will be nicely handled but my scheme.

> Consider:
>
> struct Point { unsigned x, y; const char* color; }

Good example. If the color is NULL then you don't need to call the
dtor. If it isn't then the object is not a zombie.

Of course you probably can devise clever examples where:
a) a resource is owned via a zero handle or
b) you really want to distinguish between zombie and all-zero state
for some reason I cannot yet imagine ;-) (I'd appreciate any such
example)

In this case the easy workaround is to add

bool is_initialized;

member to such class set it to true in ctor and false in dtor.

The reason I know this issue isn't such a big deal is because the
approach I described is simply an automatic version of a manual one
currently used in Java, C# and C++/CLI. There too theoretically it is
impossible to distinguish between an zero object and the one just
before the constructor/zombie state but it doesn't seem to cause any
significant problems.

--
Eugene

James Hopkin

unread,
Sep 25, 2008, 5:31:35 PM9/25/08
to
On Sep 24, 2:08 am, JoshuaMaur...@gmail.com wrote:
>
> First, let me rephrase your code so it actually compiles.
>
> class object {};
> void foo(bool a)
> { if (a) goto v2;

> v1: object a1;
> v2: object a2;
> v3: ;
>
> }
>
> The previous will compile with
comeau.http://www.comeaucomputing.com/tryitout/
>

That only compiles because both object's constructor and destructor
are trivial.

You can get a similar effect with the ?: operator, though:

const object& obj = a ? existing_obj : object();

James


--

Andrei Alexandrescu

unread,
Sep 25, 2008, 5:46:40 PM9/25/08
to
David Abrahams wrote:
> on Wed Sep 24 2008, Andrei Alexandrescu <SeeWebsiteForEmail-AT-erdani.org>
wrote:
>
>> Either an operation does the work on
>> the side and swap()s it with the original object, or first saves a
>> copy of the object, operates on the original object, and in case that
>> fails swaps the old content back into the object.
>
> Or neither. Take a look at vector<T>::push_back.
>
> The standard library gives the strong guarantee in precisely those cases
> where it doesn't have to make a copy/swap sacrifice.
>
>> Surprise, surprise, the requirement that rollback be nothrow is the
>> moral equivalent of the commit being nothrow.
>
> I don't understand the point you're trying to make here, but I think
> your premise above isn't quite right, so you might want to reconsider in
> that light.

My premise was right all right. I was comparing and contrasting Marsh
Ray's considerations of no-throw commit vs. reversible operations and
pointing out they might be converses of each other: one does the work on
the side and commits with a nothrow operation, the other works on the
target object and remembers its history so it can rollback with a
nothrow operation. Perusing his post might help.

Andrei

--

David Abrahams

unread,
Sep 25, 2008, 5:49:11 PM9/25/08
to

on Thu Sep 25 2008, Andrei Alexandrescu <SeeWebsiteForEmail-AT-erdani.org>
wrote:

>>> Great question. Objects can live on the stack. If the seatbelt is on,


>>> escaping of addresses of locals is statically verboten.
>>
>> Then this is not C++. I might've guessed, but I wish that had been
>> clear from the start.
>
> I think there would be a lot of merit in the notion of a safe C++ in
> which certain operations are statically rejected.

Perhaps so, but if you are *really* trying to design D 3.0 here, I wish
I'd known it from the start.

> We've gone some of that way already, with three-argument std::copy
> raising an increasing amount of eybrows

Not sure what you're getting at, but you're not sticking up for
_SECURE_SCL in release mode, I hope.

> and with compiler warnings at strcpy, sprintf et al.
>
> Aout the larger discussion, upon more thinking, I was really
> hard-pressed to come up with valid designs resuscitating zombie
> objects. I think it's better to say that once a destructor has run,
> it should mark the resource as just gone.

So... you spend a bit for the marker and you don't allow the program to
act as though a destroyed object has just been created?

> The object remains in there for type safety, so behavior is
> reproducible,

Which reproducible behavior would you choose?

> but should refuse to reallocate resources because it can't release
> them.

You mean, because you assume it won't be destroyed again before being
leaked?

> Not very attractive...

I guess I haven't really understood what your goals were from the
beginning, so it's not so easy to see what you'll find attractive.
Maybe you should tell us what you're trying to accomplish.

--
Dave Abrahams
BoostPro Computing
http://www.boostpro.com

[ See http://www.gotw.ca/resources/clcm.htm for info about ]

Erik Wikström

unread,
Sep 25, 2008, 5:54:07 PM9/25/08
to
On 2008-09-25 11:52, Eugene Gershnik wrote:
> Andrei Alexandrescu wrote:
>>
>> There's one thing with this approach, that it requires the
>> zero-initialized state to be a sort of bad state.
>>
>
> This isn't a big limitation and, in the rare case it might turn out to
> be one, there is an easy workaround (see below). Yes is_zombie() will
> return true for an object that is all zeroes but why is it bad? Your
> code may contain:
>
> if (!is_zombie(p))
> delete p;
>
> so for an empty struct Point { unsigned x, y; } you will not call the
> destructor but so what? This class doesn't need the destructor at all
> to begin with. The only time you need a destructor is if you have a
> non-null pointer or, possibly, some kind of non-zero integral "handle"
> as a member. And this will be nicely handled but my scheme.

Since all objects must behave safely when in a zombie-state you would
have to make the zombie-check in each member-function where the zombie
state could be harmful. Consider the following member-function in an
object where all-zero is a valid state:

void Class::SomeFunct()
{
if (!is_zombie(this))
{
// Do useful work
}
}

>> Consider:
>>
>> struct Point { unsigned x, y; const char* color; }
>
> Good example. If the color is NULL then you don't need to call the
> dtor. If it isn't then the object is not a zombie.
>
> Of course you probably can devise clever examples where:
> a) a resource is owned via a zero handle or
> b) you really want to distinguish between zombie and all-zero state
> for some reason I cannot yet imagine ;-) (I'd appreciate any such
> example)
>
> In this case the easy workaround is to add
>
> bool is_initialized;
>
> member to such class set it to true in ctor and false in dtor.
>
> The reason I know this issue isn't such a big deal is because the
> approach I described is simply an automatic version of a manual one
> currently used in Java, C# and C++/CLI. There too theoretically it is
> impossible to distinguish between an zero object and the one just
> before the constructor/zombie state but it doesn't seem to cause any
> significant problems.

I don't know about Java, but I doubt that the CLR allows you to access
an object before it has been constructed, it would not be type-safe. And
any object of class-type have a type-object pointer which will always be
non-zero for a valid object.

--
Erik Wikström

Francis Glassborow

unread,
Sep 25, 2008, 5:58:58 PM9/25/08
to
Andrei Alexandrescu wrote:

>
> There's one thing with this approach, that it requires the
> zero-initialized state to be a sort of bad state. Consider:
>
> struct Point { unsigned x, y; const char* color; }
>
> In some design a zero-initialized point could mean a black point at the
> origin. The problem is that that's also a zombie point, which comes off
> as a surprise.
>
> Other than that, this is a pretty good scheme. But I see the above a
> major issue.
>
>

You are not kidding. There are many types where all bits zero are
perfectly good values for the type. Indeed most types do not have
natural 'trap values'. Providing a 'trap value' for zombie state almost
certainly requires extra storage. Even some classes that normally
acquire resources can still have valid values without the resources
having been acquired.

In the case of polymorphic types we might be able to do something by
making the vpnrt (assuming the implementation has such a thing) have a
special value (e.g. have it point to a zombie indicator)


--
Note that robinton.demon.co.uk addresses are no longer valid.

Andre Kaufmann

unread,
Sep 25, 2008, 5:51:38 PM9/25/08
to
Joshua...@gmail.com wrote:
> On Sep 22, 5:33 pm, Andre Kaufmann <akfmn...@t-online.de> wrote:
>> JoshuaMaur...@gmail.com wrote:
> [...]
> Yes.
> Rhetorical question: should we not use templates because they weren't
> fully supported in some old compilers?

Well, I think we shouldn't discuss about any old compiler, but only the
fact that the implementation details for exceptions have changed.

>> By the way this compiler eventually had to track non linear control flow
>> too, caused by raised structured exceptions (which are not standard C++).
>

> If true, a severe flaw in the VS 2003 compiler and its language
> extensions IMO.

I don't think that is severe - if the compiler keeps still track of the
created objects and destroys them correctly. VS2003 had some flaws
regarding exception handling, I agree.
IIRC this has been corrected in VS2005 and you have to choose the
exception model, which your code supports - surely standard C++ only
supports one of them - but I wouldn't call it a flaw being able to
handle processor exceptions / exception model of other languages
optionally if I want to.

I don't use this exception model, however, since the code size is larger
than with the standard C++ exception model - at least for VC. Haven't
had a look at the generated assembly code of GCC yet.

The only valid reaction IMHO for such irregular exceptions (e.g. access
violation) is to exit the application and write some debug information -
eventually try to save the current data to a backup location.

> [...]
> The previous will NOT compile, nor will it compile if you declare a
> constructor or declare a non-POD member. Your goto example is actually

Sorry - my mistake, I've missed to add an destructor to my test code, so
it compiled and I wondered therefore how static exception lookup tables
could work.

> invalid, as it cannot happen in conforming code. You are not allowed
> to skip non-POD object definitions in switch statements and with gotos
> for this exact reason (and possibly others).

I agree, it would be too dangerous to allow such irregular control flow
and accessing uninitialized objects on the stack.
Anyways there are conditions, processor exceptions (access violation,
division by zero, security issues etc.) which might have an impact on
the standard control flow.

> You absolutely can map the current program counter to which
> (automatic, aka stack) objects have been successfully constructed.

O.k. I think I will agree so far - at least not deny it ;-).
Although I think it isn't that simple to implement such a table, if I
think of code relocation / global optimization at the linker stage etc.

> Now, I presume all compilers do array initialization in a loop, so
> array initialization would require a little extra work as the
> exception handler would have to have access to which elements in the
> array have been constructed, but this is solvable.
>
> It is my impression that all other problems are also solvable.


> [...]
> I give you the code bloat might make the size of the executable on
> disk unacceptable, but it won't affect runtime performance in any
> significant way if correctly done (assuming virtual memory).

Well regarding code bloat (templates) I had a code size difference of 3
MB compared to 8 MB. I don't know if the code bloat has an effect on the
runtime.

There are 3 golden rules regarding code performance one should follow:

a) Measure
b) Measure
c) Measure

> The exception code will be off by itself, meaning with a virtual
> memory setup, nearly all of the time the exception handling code will
> be paged out to disk. We pay little no cost to have this exception
> handling code around if we don't use it.

> Now, I agree that with all of the crazy awesome optimizations done
> under the hood, it's hard to gauge hand coding hacks would help and by
> how much. However, you would have to work pretty hard to contrive an
> example where having the exact same instructions in the exact same
> order except with some removed would hurt runtime performance.
>
> (Note that this does assume that exceptions are the rare case. They
> are intended as the rare case. Using them as "normal" returns will
> greatly hurt runtime performance.)

I had actually such an example. But it had only a significant impact,
because the code has been executed in a loop called a million times.
Also I remember one example of memory copying, where 2 copy operations -
first to a small local stack buffer and then to the destination was
faster than directly copying to the destination.

Andre


--

Bart van Ingen Schenau

unread,
Sep 25, 2008, 5:53:31 PM9/25/08
to
Andrei Alexandrescu wrote:

> James Hopkin wrote:
>> On Sep 23, 1:29 am, Andrei Alexandrescu
>> <SeeWebsiteForEm...@erdani.org> wrote:
>>> Mathias Gaunard wrote:
>>>> All default constructors of containers are not required to be
>>>> nothrow. Electronic Arts has asked the standard to change that,
>>>> however, showing their own EASTL as an improvement upon the STL.
>>> I'm of the same opinion as them.
>>>
>>> Andrei
>>>
>>
>> Me too, but I don't think it implies much about whether throwing
>> default constructors *in general* are problematic.
>
> I agree. So let me restart. Do you agree that in a GC system, there
> should be _some_ constructor that doesn't throw (that being the
> constructor called when the object enters the destroyed state)? If we
> do agree on that, what remains is to define that constructor.

No, I don't agree that *every* class must have a no-throw constructor.
If a class has a natural state that can be reached without performing
operations that can potentially throw, then that particular class
should have a no-throw constructor.
But I am not willing to introduce an artificial zombie-state just to
keep the people happy that don't like throwing constructors.

In my opinion, a wrapper around an OS mutex should not have a state
where it does not own a mutex. If getting a mutex from the OS can
throw, then this wrapper class will not have any no-throw constructors.
(And using it after the destructor has returned the mutex to the OS is a
bug.)

>
> Andrei
>
>
Bart v Ingen Schenau
--
a.c.l.l.c-c++ FAQ: http://www.comeaucomputing.com/learn/faq
c.l.c FAQ: http://c-faq.com/
c.l.c++ FAQ: http://www.parashift.com/c++-faq-lite/

Eugene Gershnik

unread,
Sep 25, 2008, 8:50:43 PM9/25/08
to
Francis Glassborow wrote:

>
> There are many types where all bits zero are
> perfectly good values for the type.

Which simply means that the post-destructor (aka zombie) state is a
perfectly good state for this type. I think everybody got distracted
by the fact that under Andrei's scheme every public member function
must check for "zombieness" and so you really need to be able to
distinguish this state. This error-prone thing is exactly what I want
to *avoid*. In my scheme the only time you need to check if the object
was already destructed is if you want to delete it. No other code
should care about it.
So for the purposes of delete the critical observation is that an
object in a *valid* all-zero state would not need destruction to begin
with.
Obviously for this to work and be "safe" dereferencing a NULL pointer,
NULL reference and a "zero" handle should have well-defined failure
semantics provided either by platform (the ones I am familiar with all
do) or the language.

--
Eugene


--

It is loading more messages.
0 new messages