Deterministic destruction computationally expensive?

13 views
Skip to first unread message

Anand Hariharan

unread,
Nov 15, 2004, 6:37:59 AM11/15/04
to
I recently attended a talk given by a .NET evangelist. Surprisingly,
the speaker was quite sincere and explained why Garbage collection is
no panacea (citing examples such as database connections and file
handles), taking his presentation through muddy waters of Dispose,
Close, etc.

At one point he showed how C# chose to overload the "using" keyword in
a completely unrelated context viz., to specify that the variables
within a block defined by "using" should be destroyed as soon as they
leave the scope.

At that point I asked why C# could not have simply incorporated those
semantics as a part of the language rather than requiring the
programmer explicitly request it at specific places. His response:
"Deterministic de-construction is generally expensive, especially if
one has several small objects sporadically sprewn all over."

Is there a merit (statistical/empirical) to his assertion? I thought
C++ went great lengths for RAII to be possible, eschewing runtime
guzzlers (such as mark-and-sweep) largely on performance grounds.

thank you for listening,
- Anand Hariharan

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

Francis Glassborow

unread,
Nov 15, 2004, 4:40:19 PM11/15/04
to
In article <22868227.04111...@posting.google.com>, Anand
Hariharan <mailto.anan...@gmail.com> writes

>Is there a merit (statistical/empirical) to his assertion? I thought
>C++ went great lengths for RAII to be possible, eschewing runtime
>guzzlers (such as mark-and-sweep) largely on performance grounds.

I think people often forget that C++ uses a single threaded abstract
machine where RAII works happily. C#, among other languages, uses a
multi-threaded abstract machine where GC has some extra advantages.


--
Francis Glassborow ACCU
Author of 'You Can Do It!' see http://www.spellen.org/youcandoit
For project ideas and contributions: http://www.spellen.org/youcandoit/projects

Ivan Vecerina

unread,
Nov 15, 2004, 4:38:42 PM11/15/04
to
"Anand Hariharan" <mailto.anan...@gmail.com> wrote in message
news:22868227.04111...@posting.google.com...

> I recently attended a talk given by a .NET evangelist. Surprisingly,
> the speaker was quite sincere and explained why Garbage collection is
> no panacea (citing examples such as database connections and file
> handles), taking his presentation through muddy waters of Dispose,
> Close, etc.
Not such a big surprise, since compared to Java, C# has somewhat
better integrated support for explicit destruction.

> At one point he showed how C# chose to overload the "using" keyword in
> a completely unrelated context viz., to specify that the variables
> within a block defined by "using" should be destroyed as soon as they
> leave the scope.
>
> At that point I asked why C# could not have simply incorporated those
> semantics as a part of the language rather than requiring the
> programmer explicitly request it at specific places. His response:
> "Deterministic de-construction is generally expensive, especially if
> one has several small objects sporadically sprewn all over."
>
> Is there a merit (statistical/empirical) to his assertion? I thought
> C++ went great lengths for RAII to be possible, eschewing runtime
> guzzlers (such as mark-and-sweep) largely on performance grounds.

The assertion definitely does not apply to stack-allocated C++ objects.
However, when class instances (or their data members) are allocated on
the heap, as is most often the case in C# and Java, explicit memory
deallocation can be more expensive than to wait for the next GC cycle.

It all really depends on the memory allocation algorithm that is
being used. In some approaches freeing memory can be as simple
as setting flag in the header of the allocated block; in others
it may require a complex look-up and table update process.

hth,
Ivan
--
http://ivan.vecerina.com/contact/?subject=NG_POST <- email contact form

M Jared Finder

unread,
Nov 16, 2004, 6:31:54 AM11/16/04
to
Anand Hariharan wrote:
> I recently attended a talk given by a .NET evangelist. Surprisingly,
> the speaker was quite sincere and explained why Garbage collection is
> no panacea (citing examples such as database connections and file
> handles), taking his presentation through muddy waters of Dispose,
> Close, etc.
>
> At one point he showed how C# chose to overload the "using" keyword in
> a completely unrelated context viz., to specify that the variables
> within a block defined by "using" should be destroyed as soon as they
> leave the scope.
>
> At that point I asked why C# could not have simply incorporated those
> semantics as a part of the language rather than requiring the
> programmer explicitly request it at specific places. His response:
> "Deterministic de-construction is generally expensive, especially if
> one has several small objects sporadically sprewn all over."
>
> Is there a merit (statistical/empirical) to his assertion? I thought
> C++ went great lengths for RAII to be possible, eschewing runtime
> guzzlers (such as mark-and-sweep) largely on performance grounds.

This just seems crazy. I can't see how C#'s using, Java's try-finally,
or C++'s automatic destruction would generate code that is different in
any way. I can see there being problems with an old ABI that requires
each function to register itself as having a cleanup step, but standards
can't prevent all stupid implementations. In addition, using garbage
collection will remove much of the work done in destructors since a most
of the resources used in a program tends to be memory.

I'd be interested in what Herb Sutter had to say about this, considering
that one of the big advantages of C++/CLI over C# is the automatic
calling of destructors.

-- MJF

James Dennett

unread,
Nov 16, 2004, 6:39:10 AM11/16/04
to
Francis Glassborow wrote:
> In article <22868227.04111...@posting.google.com>, Anand
> Hariharan <mailto.anan...@gmail.com> writes
>
>>Is there a merit (statistical/empirical) to his assertion? I thought
>>C++ went great lengths for RAII to be possible, eschewing runtime
>>guzzlers (such as mark-and-sweep) largely on performance grounds.
>
>
> I think people often forget that C++ uses a single threaded abstract
> machine where RAII works happily. C#, among other languages, uses a
> multi-threaded abstract machine where GC has some extra advantages.

For the most part, RAII also works well in multi-threaded situations.
Most things aren't shared between threads, in well-designed apps.

-- James

David Abrahams

unread,
Nov 16, 2004, 9:10:15 PM11/16/04
to
James Dennett <jden...@acm.org> writes:

> Francis Glassborow wrote:
> > In article <22868227.04111...@posting.google.com>, Anand
> > Hariharan <mailto.anan...@gmail.com> writes
> >
> >>Is there a merit (statistical/empirical) to his assertion? I thought
> >>C++ went great lengths for RAII to be possible, eschewing runtime
> >>guzzlers (such as mark-and-sweep) largely on performance grounds.
> >
> >
> > I think people often forget that C++ uses a single threaded abstract
> > machine where RAII works happily. C#, among other languages, uses a
> > multi-threaded abstract machine where GC has some extra advantages.
>
> For the most part, RAII also works well in multi-threaded situations.
> Most things aren't shared between threads, in well-designed apps.

Also, I think with a lock-free memory allocator many of the
performance advantages of GC disappear even for the multithreaded
case.

--
Dave Abrahams
Boost Consulting
http://www.boost-consulting.com

ka...@gabi-soft.fr

unread,
Nov 16, 2004, 9:09:02 PM11/16/04
to
mailto.anan...@gmail.com (Anand Hariharan) wrote in message
news:<22868227.04111...@posting.google.com>...

> I recently attended a talk given by a .NET evangelist. Surprisingly,
> the speaker was quite sincere and explained why Garbage collection is
> no panacea (citing examples such as database connections and file
> handles), taking his presentation through muddy waters of Dispose,
> Close, etc.

> At one point he showed how C# chose to overload the "using" keyword in
> a completely unrelated context viz., to specify that the variables
> within a block defined by "using" should be destroyed as soon as they
> leave the scope.

> At that point I asked why C# could not have simply incorporated those
> semantics as a part of the language rather than requiring the
> programmer explicitly request it at specific places. His response:
> "Deterministic de-construction is generally expensive, especially if
> one has several small objects sporadically sprewn all over."

> Is there a merit (statistical/empirical) to his assertion? I thought
> C++ went great lengths for RAII to be possible, eschewing runtime
> guzzlers (such as mark-and-sweep) largely on performance grounds.

There's certainly nothing wrong with his assertion; it's true as far as
it goes. I'm not familiar with C#, but I seem to recall reading that it
doesn't have auto variables (at least with object types); this is what
makes RAII work so well in C++. (Note that if you allocate an object
with new in C++, you also have to do something explicit to get its
destructor called when you leave the scope.) As for using, I'm not sure
why it's better than finally -- it's still the user who states what has
to be destroyed, and not the author of the class.

With regards to the performance issues, I believe that this was the case
when C++ was first being defined. Today, of course, garbage collection
typically outperforms manual memory management. And while neither do as
well on performance grounds as no memory management, i.e. variables
allocated directly on the stack, the different semantics they provide
are IMHO an even stronger argument for auto and global variables.

--
James Kanze GABI Software http://www.gabi-soft.fr
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34

Francis Glassborow

unread,
Nov 16, 2004, 9:13:46 PM11/16/04
to
In article <_Semd.145189$hj.10661@fed1read07>, James Dennett
<jden...@acm.org> writes

> > I think people often forget that C++ uses a single threaded abstract
> > machine where RAII works happily. C#, among other languages, uses a
> > multi-threaded abstract machine where GC has some extra advantages.
>
>For the most part, RAII also works well in multi-threaded situations.
>Most things aren't shared between threads, in well-designed apps.

Indeed but the rub is in the first phrase. 'For the most part' allows
for times when it is not so. It is this kind of problem that is implicit
in using a single threaded abstract machine.

--
Francis Glassborow ACCU
Author of 'You Can Do It!' see http://www.spellen.org/youcandoit
For project ideas and contributions: http://www.spellen.org/youcandoit/projects

Alf P. Steinbach

unread,
Nov 17, 2004, 6:11:17 AM11/17/04
to
* ka...@gabi-soft.fr:

>
> With regards to the performance issues, I believe that this was the case
> when C++ was first being defined. Today, of course, garbage collection
> typically outperforms manual memory management. And while neither do as
> well on performance grounds as no memory management, i.e. variables
> allocated directly on the stack, the different semantics they provide
> are IMHO an even stronger argument for auto and global variables.

"Of course ... outperforms",
"An even stronger argument for global variables",
are you trolling?

Anyway, I fail to believe most of the assertions bandied about in this
thread (not just those given by you in the paragraph above).

And the OP was asking about the cost of _deterministic_ destruction, which
is not at all in conflict with automatic garbage collection. For example,
a guarantee that an object has its destructor invoked at the point where
the last reference to the object is removed, if that happens, and otherwise
as with asynchronous garbage collection; for an example of what that would
solve, it would guarantee that external objects such as COM objects were
released in a timely manner. What is the connection to threading there
(no remaining references -> no threading issues, as I see it)? Why would it
or could it be inefficient? What does actual experience say?

--
A: Because it messes up the order in which people normally read text.
Q: Why is it such a bad thing?
A: Top-posting.
Q: What is the most annoying thing on usenet and in e-mail?

Dave Harris

unread,
Nov 17, 2004, 6:13:48 AM11/17/04
to
mailto.anan...@gmail.com (Anand Hariharan) wrote (abridged):

> At that point I asked why C# could not have simply incorporated those
> semantics as a part of the language rather than requiring the
> programmer explicitly request it at specific places. His response:
> "Deterministic de-construction is generally expensive, especially if
> one has several small objects sporadically sprewn all over."
>
> Is there a merit (statistical/empirical) to his assertion? I thought
> C++ went great lengths for RAII to be possible, eschewing runtime
> guzzlers (such as mark-and-sweep) largely on performance grounds.

If you've already got garbage collection, the incremental cost of using it
in a specific case is probably lower than using deterministic destruction
in that case. Note that your quote doesn't claim that RAII is slower than
try/finally (or whatever the C# syntax is). In other words, it doesn't
answer your question directly. He's saying all forms of deterministic
destruction are relatively expensive. He may be arguing that having RAII
in the language would lead to it being used unnecessarily, and so make
some programmes unnecessarily slow.

For his general point, compare:

void with_gc() {
SomeObject *ptr = new SomeObject:
some_proc();
}

with:

void with_raii() {
std::auto_ptr<SomeObject> ptr( new SomeObject );
some_proc();
}

Clearly the latter routine will have some extra code for the destructor of
the auto_ptr, which will make it slower. Also, in the first routine the
call to some_proc() is a tail-call, so can be optimised into a jump, but
the destructor prevents that optimisation in with_raii().

A good GC implementation will not have any offsetting inefficiencies. The
allocation cost will be the same, and deallocation is basically free - GC
costs are mainly proportional to the number of live objects. So it's a net
win for GC (given that it is present and running and the object is on the
heap anyway).

-- Dave Harris, Nottingham, UK

Francis Glassborow

unread,
Nov 17, 2004, 7:05:55 PM11/17/04
to
In article <419ab671....@news.individual.net>, Alf P. Steinbach
<al...@start.no> writes

>What is the connection to threading there
>(no remaining references -> no threading issues, as I see it)? Why would it
>or could it be inefficient? What does actual experience say?

The use of GC assumes that there is no other way to hold an access to an
object other than by references. That means that there are far fewer
programming issues in languages that do not provide other ways to access
an object. Without some measure of language support GC is expensive in
so far as writing correct code is concerned.

The problem with RAII is that dtors normally need to apply some form of
lock to update the count mechanism and that lock is applied even when
the object is only ever referenced by a single thread. Any form of lock
mechanism in a system that has more than one effective processor can
proof very expensive. Currently C++ has no concept that code may
actually be executed in parallel.

Note that it is far from trivial to design a correct multi-threading
model (Java originally got it wrong).

With care, multi-threading built on top of a single-threaded abstract
machine can be made to work as long as the real machine actually
executes as a single thread, as soon as the real machine departs from
being a realisation of the abstract machine we are in trouble; either we
have to force the real machine to behave as if it were single threaded
or we have to rely on language extensions that are probably not
portable. The really nasty thing about the latter route is that those
extensions may be purely semantic without any syntactic visibility; i.e.
the actual behaviour of our code is implementation dependent.


--
Francis Glassborow ACCU
Author of 'You Can Do It!' see http://www.spellen.org/youcandoit
For project ideas and contributions: http://www.spellen.org/youcandoit/projects

Peter Dimov

unread,
Nov 18, 2004, 7:25:07 AM11/18/04
to
bran...@cix.co.uk (Dave Harris) wrote in message news:<memo.20041116215931.2492C@brangdon.m>...

> For his general point, compare:
>
> void with_gc() {
> SomeObject *ptr = new SomeObject:
> some_proc();
> }
>
> with:
>
> void with_raii() {
> std::auto_ptr<SomeObject> ptr( new SomeObject );
> some_proc();
> }
>
> Clearly the latter routine will have some extra code for the destructor of
> the auto_ptr, which will make it slower. Also, in the first routine the
> call to some_proc() is a tail-call, so can be optimised into a jump, but
> the destructor prevents that optimisation in with_raii().

These two examples are not equivalent, so efficiency doesn't matter.
with_raii() executes SomeObject::SomeObject(), some_proc(),
SomeObject::~SomeObject(). with_gc() omits the last call.

Glen Low

unread,
Nov 19, 2004, 8:01:24 AM11/19/04
to
> At that point I asked why C# could not have simply incorporated those
> semantics as a part of the language rather than requiring the
> programmer explicitly request it at specific places. His response:
> "Deterministic de-construction is generally expensive, especially if
> one has several small objects sporadically sprewn all over."

Note that C# has the notion of "value types" (unlike Java), which are
generally stack-based and are scoped like auto variables in C++.
They're not as flexible as C++ auto variables since the choice of
being stack-based is a design decision not a client decision, but they
are often used as the "small objects sporadically strewn all over".

Your question however seemed to be about syntax rather than
implementation or performance -- to paraphrase, "given that I need to
deconstruct an object anyway, why do I need to write it explicitly?"
Knowing that coding up a "using" scope (in C#) or leaving the auto
variable to fall out of scope (in C++) have identical implementations
i.e. run the (expensive) destructor routine & deallocate the heap
memory if any, what does his response really mean?

I think he's not saying "GC is better than RAII" -- that's purely a
performance question as the other posters have said -- but rather, "we
made this syntax to highlight the expense of a deterministic
deconstruction, e.g. the closing of a file". Much like in C++, we have
"reinterpret_cast" to highlight a dangerous C-style cast.

What other syntax choices could be made and still preserve the
simplicity of the language? Given that the object is a "reference
type", it is always allocated on the heap. Given that the default
behavior of heap objects is to be garbage collected at an
indeterminate time, how do you indicate that you want to run the
destructor at the end of scope?

Cheers,
Glen Low, Pixelglow Software
www.pixelglow.com

ka...@gabi-soft.fr

unread,
Nov 19, 2004, 9:55:27 AM11/19/04
to
al...@start.no (Alf P. Steinbach) wrote in message
news:<419ab671....@news.individual.net>...
> * ka...@gabi-soft.fr:

> > With regards to the performance issues, I believe that this was the
> > case when C++ was first being defined. Today, of course, garbage
> > collection typically outperforms manual memory management. And
> > while neither do as well on performance grounds as no memory
> > management, i.e. variables allocated directly on the stack, the
> > different semantics they provide are IMHO an even stronger argument
> > for auto and global variables.

> "Of course ... outperforms",
> "An even stronger argument for global variables",
> are you trolling?

Just stating what I would consider rather obvious facts, mainly:

- Real measurements comparing modern garbage collection and manual
memory management tend to show garbage collection to be faster.
Although, like most benchmarks, it's better to be leary -- YMMV, as
they say.

- Global and auto variables have different semantics (lifetime, etc.)
than dynamic variables. I would have thought that in this group, no
one would disagree with that, and that mighty few, if any, would
argue that these semantics are not useful, and that it would be
better to just allocate everything dynamically.

The real argument for global and auto variables is thus, IMHO, the
additional behavior due to their different semantics. That they are
also faster than dynamic allocation is just icing on the cake.

> Anyway, I fail to believe most of the assertions bandied about in this
> thread (not just those given by you in the paragraph above).

> And the OP was asking about the cost of _deterministic_ destruction, which
> is not at all in conflict with automatic garbage collection.

The OP was asking about a statement concerning C# compared to C++.
IMHO, the statement actually avoided the real issues -- deterministic
destruction of variables which would otherwise be garbage collected may
be more expensive than just letting garbage collection do its work, but
of course, that's NOT what C++ offers. The difference between all
variables being dynamically allocated, and only those you want being
dynamically allocated, is not irrelevant. Nor is the fact that judging
from his comments, the declaration which triggers the "deterministic
de-construction" in C# is still in the user code (like finally in Java),
and does not depend on the object or its type. IMHO, this means that it
is NOT really an improvement on finally, and is still far from RAII as
we understand it in C++.

(The rest of your comments seemed to concern mainly COM, which I know
nothing about, so I'll let others answer them.)

--
James Kanze GABI Software http://www.gabi-soft.fr
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34

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

ka...@gabi-soft.fr

unread,
Nov 19, 2004, 9:55:48 AM11/19/04
to
bran...@cix.co.uk (Dave Harris) wrote in message
news:<memo.20041116215931.2492C@brangdon.m>...

> A good GC implementation will not have any offsetting inefficiencies.


> The allocation cost will be the same, and deallocation is basically
> free - GC costs are mainly proportional to the number of live
> objects. So it's a net win for GC (given that it is present and
> running and the object is on the heap anyway).

With a good relocating GC implementation, allocation cost will be much,
much less than with manual memory management. Actual deallocation costs
depend on different factors, and will often be higher than for manual
management (you do have to relocate still live objects), but the total
of both typically favors garbage collection. And the fact that the cost
of deallocation occurs asynchronously will, in many applications, allow
it to be shifted to dead time (e.g. while waiting for input), which
makes it effectively free.

Of course, I don't think that relocating GC is compatible with C++, as
it now stands.

--
James Kanze GABI Software http://www.gabi-soft.fr
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34

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

John Nagle

unread,
Nov 19, 2004, 10:28:37 AM11/19/04
to
Perl and Python both have deterministic destruction using
reference counts. It's noteworthy that in both of those languages,
memory allocation is rarely a major concern of the programmer.
That's a sign they're doing something right.

There are two basic objections to reference counts -
overhead and circular references.

Perl deals with the second problem by offering
"weak references". If you have a tree with backlinks,
the backlinks should be weak references. The object
goes away when the last ordinary (strong) reference
goes away, regardless of weak references. This will
prevent memory leaks in the more common cases. This is
not leakproof, but the Perl approach safe against bad memory
references.

The performance problem is real, but optimizable.
For C++, it's an artifact of trying to retrofit reference
counts via templates.
Optimizers need to know about reference counts. Reference
count updates can potentially be hoisted out of innner loops
using standard optimization techniques, if the compiler knows
something about reference counts.

If you had optimization of reference counts and subscript
checking at compile time, you could have the safety of Perl
and Python with the speed of C/C++.

This isn't going to happen, because the current C++ committee is
totally uninterested in language safety. Until some major
changes are made in the committee, C++ will not become any
safer, and we'll continue to have very unreliable software
in that language.

John Nagle
Animats
Francis Glassborow wrote:

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

Branimir Maksimovic

unread,
Nov 19, 2004, 11:49:47 AM11/19/04
to
mailto.anan...@gmail.com (Anand Hariharan) wrote in message news:<22868227.04111...@posting.google.com>...
....

> At that point I asked why C# could not have simply incorporated those
> semantics as a part of the language rather than requiring the
> programmer explicitly request it at specific places. His response:
> "Deterministic de-construction is generally expensive, especially if
> one has several small objects sporadically sprewn all over."
I can add:
Non deterministic de-construction is generally expensive, especially
if one has limited resources. :)

>
> Is there a merit (statistical/empirical) to his assertion? I thought
> C++ went great lengths for RAII to be possible, eschewing runtime
> guzzlers (such as mark-and-sweep) largely on performance grounds.
>

Assertion would be true if nowadays systems could have unlimited
resources. But presence of "using", "IDisaposable" interface and
similar hacks proves that wrong.
Problem is that GC solution is *forced*, but there is no universal
sledge hammer for memory or resource utilization.
For example when implementing malloc we used two layer technique.
Bottom layer is responisble for good memory utilization( not for speed),
while top layer is responsible for performance (especially when dealling
with threads). This is ok for apps that allocate/deallocate memory chunks
frequently, but not good for one's that allocate lot's of chunks
then deallocate.

Greetings, Bane.

Mike Capp

unread,
Nov 19, 2004, 11:51:42 AM11/19/04
to
ka...@gabi-soft.fr wrote in message news:<d6652001.04111...@posting.google.com>...

> There's certainly nothing wrong with his assertion; it's true as far as
> it goes. I'm not familiar with C#, but I seem to recall reading that it
> doesn't have auto variables (at least with object types); this is what
> makes RAII work so well in C++.

C# has "structs", which are user-defined value types allocated on the
stack. In that sense they are effectively auto. Unfortunately they're
rather restrictive - not quite POD, but heading that way. In
particular, you can't define destructors for them, which makes them
entirely useless for RAII.

C# also makes the spectacular blunder of using identical syntax for
(non-primitive) value and reference semantics, which is a perpetual
source of exciting new bugs.

> As for using, I'm not sure
> why it's better than finally

It's terser but less flexible. Both approaches become very ugly when
you have multiple objects in a scope that require cleanup on exit.

cheers,
Mike

Anand Hariharan

unread,
Nov 20, 2004, 5:20:45 AM11/20/04
to
pdi...@gmail.com (Peter Dimov) wrote in message news:<abefd130.04111...@posting.google.com>...

> bran...@cix.co.uk (Dave Harris) wrote in message news:<memo.20041116215931.2492C@brangdon.m>...
> > For his general point, compare:
> >
> > void with_gc() {
> > SomeObject *ptr = new SomeObject:
> > some_proc();
> > }
> >
> > with:
> >
> > void with_raii() {
> > std::auto_ptr<SomeObject> ptr( new SomeObject );
> > some_proc();
> > }
> >
> > Clearly the latter routine will have some extra code for the destructor of
> > the auto_ptr, which will make it slower. Also, in the first routine the
> > call to some_proc() is a tail-call, so can be optimised into a jump, but
> > the destructor prevents that optimisation in with_raii().
>
> These two examples are not equivalent, so efficiency doesn't matter.
> with_raii() executes SomeObject::SomeObject(), some_proc(),
> SomeObject::~SomeObject(). with_gc() omits the last call.
>

Wasn't that /exactly/ Dave Harris' point? The two examples /can/ be
compared since both envisage calling the default constructor of
'SomeObject' followed by a call to some_proc. 'with_raii()' does more
(viz., calling the destructor of 'SomeObject') but without the
overhead of GC. Dave further makes a case that an implementation that
runs GC allows the optimiser to improve 'with_gc()'.

What did I miss?

- Anand

Alf P. Steinbach

unread,
Nov 20, 2004, 5:31:17 AM11/20/04
to
* ka...@gabi-soft.fr:

> al...@start.no (Alf P. Steinbach) wrote in message
> news:<419ab671....@news.individual.net>...
> > * ka...@gabi-soft.fr:
>
> > > With regards to the performance issues, I believe that this was the
> > > case when C++ was first being defined. Today, of course, garbage
> > > collection typically outperforms manual memory management. And
> > > while neither do as well on performance grounds as no memory
> > > management, i.e. variables allocated directly on the stack, the
> > > different semantics they provide are IMHO an even stronger argument
> > > for auto and global variables.
>
> > "Of course ... outperforms",
> > "An even stronger argument for global variables",
> > are you trolling?
>
> Just stating what I would consider rather obvious facts, mainly:
>
> - Real measurements comparing modern garbage collection and manual
> memory management tend to show garbage collection to be faster.
> Although, like most benchmarks, it's better to be leary -- YMMV, as
> they say.
>
> - Global and auto variables have different semantics (lifetime, etc.)
> than dynamic variables. I would have thought that in this group, no
> one would disagree with that, and that mighty few, if any, would
> argue that these semantics are not useful, and that it would be
> better to just allocate everything dynamically.

OK, I see partly what you mean now -- those are two very different
issues, but the earlier formulation led me to believe you had them
lumped into one; what I'm still unclear on is the "_even_ stronger".

There is another dimension here that is, I think, both more important
and more directly relevant to the OP's question, "deterministic
_destruction_" (the title of this thread), namely the difference between
destructor calls and memory deallocation.

I have no doubt that pure deallocation can both in principle and in
practice be more efficiently done by a general garbage collector, simply
because it's got a global view of things (memory, processor
utilization), except in the few cases where we can use a specially
crafted most efficient allocator such as a simple free-list / cache.

I do doubt that there is any benefit from non-deterministic destructor
calls, other than the dubious one of being able to turn off destructor
calls on a per-object basis (possible in C#, and AFAIK done by default
in Microsoft's Windows Forms GUI library for .NET). The reason I doubt
that there can be any benefit is two-fold: first of all, the calls will
have to made anyway, and I fail to see how they can be faster when done
by the garbage collector (other than that the garbage collector might
choose to do the calls when there is otherwise low processor
utilization); secondly, the kind of performance tweaking seemingly
necessary for professional libraries like the one mentioned here means
that not only do you not know when your destructor will be called, or if
it ever will be called (if the application exits before the object is
collected), you do not even know if there is a chance; so what possible
benefit can automatically called destructor have in for example C# and
Java (personal guideline: don't use it for anything!)?

Is there anything in the C++ standard that prohibits operator delete
from doing just the destructor call part, which I think it can do most
efficiently and in a way that can be _relied_ on, and handing the memory
deallocation bit over to a garbage collector which is better at that?


> The real argument for global and auto variables is thus, IMHO, the
> additional behavior due to their different semantics. That they are
> also faster than dynamic allocation is just icing on the cake.

I guess by "behavior", when it comes to auto variables you're referring
to RAII. Well then, see last question above. Also, the D language
allows classes to be declared 'auto', destructor call when object goes
out of scope, and according to the documentation such objects are
currently not allocated on the stack, i.e. they have the RAII semantics
regarding destructor calls but not regarding memory reclamation.


>
> > Anyway, I fail to believe most of the assertions bandied about in this
> > thread (not just those given by you in the paragraph above).
>
> > And the OP was asking about the cost of _deterministic_ destruction, which
> > is not at all in conflict with automatic garbage collection.
>
> The OP was asking about a statement concerning C# compared to C++.
> IMHO, the statement actually avoided the real issues -- deterministic
> destruction of variables which would otherwise be garbage collected may
> be more expensive than just letting garbage collection do its work, but
> of course, that's NOT what C++ offers.

I think a comparision to C++ would not be meaningful, except perhaps a
hypothetical C++ implementation with operator delete delegating memory
reclamation to a garbage collector. But even that would not capture the
idea of having C# 'using' by default. A more meaningful comparision
would, I think, be to a language based on reference counting, such as VB
6.0 -- or even better a hypothetical language like VB 6.0 semantics
but with a general garbage collector _added_.


> (The rest of your comments seemed to concern mainly COM,

Well, no, COM was just an example in the middle of a sentence. But it
was a very important example, so I'll expand on it. Substitute "file
handle", "network connection", whatever; deterministic destructor calls,
as opposed to deterministic memory reclamation, is extremely important
to have as a property of the class in question, not something the client
code must fix in every case (often forgetting, often doing wrong).

Essentially what the C# evangelist answered the OP was that D-auto-like
classes are "generally expensive, especially if one has several small
objects sporadically sprewn all over.", and that VB6-like reference
counting is "generally expensive, especially if one has several small
objects sporadically sprewn all over", and so on for every alternative
that provides deterministic destructor calls.

I think that's most probably incorrect.


> [COM] I know nothing about, so I'll let others answer them.)

Uh, that's off-topic here... ;-)

--
A: Because it messes up the order in which people normally read text.
Q: Why is it such a bad thing?
A: Top-posting.
Q: What is the most annoying thing on usenet and in e-mail?

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

Dave Harris

unread,
Nov 20, 2004, 11:17:43 AM11/20/04
to
pdi...@gmail.com (Peter Dimov) wrote (abridged):

> These two examples are not equivalent, so efficiency doesn't matter.
> with_raii() executes SomeObject::SomeObject(), some_proc(),
> SomeObject::~SomeObject(). with_gc() omits the last call.

Yes. That's the point. We're considering the effect that difference in
behaviour has on efficiency. With_gc() grants more freedom to the
implementation and so is potentially more efficient.

The .NET evangelist said "Deterministic de-construction is generally
expensive...". He is not comparing one kind of deterministic
deconstruction (eg try/catch) with another (eg RAII), he's comparing it
with non-deterministic de-construction. So my example code is to the
point.

Although the examples are not equivalent, the first can substitute for the
second (assuming no outstanding references), so it does make sense to
compare their efficiencies.

-- Dave Harris, Nottingham, UK

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

Francis Glassborow

unread,
Nov 20, 2004, 11:21:42 AM11/20/04
to
In article <j4dnd.23136$6q2....@newssvr14.news.prodigy.com>, John Nagle
<na...@animats.com> writes

> This isn't going to happen, because the current C++ committee is
>totally uninterested in language safety.

What an extraordinary statement. The Committee (a substantially
different one from that which did most of the work on C98) is
constrained by a requirement no to break legacy code without very good
reason. However within those limits they are not 'totally uninterested
in language safety' they just do not consider it the most important
issue. However as security has become of increasing importance in IT it
has a higher place now than it would have done ten years ago.

> Until some major
>changes are made in the committee, C++ will not become any
>safer, and we'll continue to have very unreliable software
>in that language.

And who do you propose make those changes? WG21 & J16 are composed of
those people who make the effort to participate. No one who wishes to be
involved is excluded, indeed we welcome newcomers as well as new
perspectives.


As for unreliable software, any language widely used by people ranging
across the entire spectrum of competence will have that problem. The
problem is much less with the tools than with those who are unable to
identify competence and listen to advice when they are given it.

--
Francis Glassborow ACCU
Author of 'You Can Do It!' see http://www.spellen.org/youcandoit
For project ideas and contributions: http://www.spellen.org/youcandoit/projects

Terje Slettebø

unread,
Nov 20, 2004, 10:13:45 PM11/20/04
to
"John Nagle" <na...@animats.com> wrote in message
news:j4dnd.23136$6q2....@newssvr14.news.prodigy.com...

> Perl and Python both have deterministic destruction using
> reference counts. It's noteworthy that in both of those languages,
> memory allocation is rarely a major concern of the programmer.
> That's a sign they're doing something right.
>
> If you had optimization of reference counts and subscript
> checking at compile time, you could have the safety of Perl
> and Python with the speed of C/C++.
>
> This isn't going to happen, because the current C++ committee is
> totally uninterested in language safety.

That's a rather sweeping remark. Well, let's look at the facts:

- The TR1 on library extensions
(http://www2.open-std.org/jtc1/sc22/wg21/docs/papers/2004/n1711.pdf)
includes reference-counted smart pointers, for safe use of dynamically
allocated memory.
- There's the STL, with its containers, also taking care of memory
allocation and release issues.
- There are proposals (such as this one:
http://www2.open-std.org/jtc1/sc22/wg21/docs/papers/2004/n1727.pdf) to
change undefined behaviour to diagnosed behaviour.
- There are also proposals for making the language safer and easier to use,
by providing new constructs with safer defaults (like "explicit classes")
and stricter type checking.

Indeed, it may be claimed that much of the reason for C++ was to provide a
better, safer, and more expressive language.

You claim the committee is not interested in language safety. However, have
you considered that language safety isn't the only concern? It typically
needs to be balanced against issues such as performance, not restricting
safe use, and backwards compatibility. Saying they're not interested in
language safety is patently untrue.

> Until some major
> changes are made in the committee, C++ will not become any
> safer, and we'll continue to have very unreliable software
> in that language.

As you know, the committee is made up of volunteers, where either themselves
or someone else (like their employer) has to pay, or contribute to, the
expenses to participate, and as such should be commended for this. Changes
won't happen unless someone who feels strongly about it, and is able to do
it, does something about it. The committee has a "ton" of issues and
proposals to deal with, already, with limited manpower. If you feel strongly
about this issue, why don't you participate, and perhaps write a paper on
this? If you think you can do better, why not join the committee?

It's easy enough to criticise someone, for not doing something you want, as
long as you don't have to do it, yourself... The expression "armchair
quarterback" comes to mind.

Regards,

Terje

David B. Held

unread,
Nov 20, 2004, 10:20:41 PM11/20/04
to
Anand Hariharan wrote:

> pdi...@gmail.com (Peter Dimov) wrote in message news:<abefd130.04111...@posting.google.com>...

> > [...]


> > These two examples are not equivalent, so efficiency doesn't matter.
> > with_raii() executes SomeObject::SomeObject(), some_proc(),
> > SomeObject::~SomeObject(). with_gc() omits the last call.
>
> Wasn't that /exactly/ Dave Harris' point? The two examples /can/ be
> compared since both envisage calling the default constructor of
> 'SomeObject' followed by a call to some_proc. 'with_raii()' does more
> (viz., calling the destructor of 'SomeObject') but without the
> overhead of GC. Dave further makes a case that an implementation that
> runs GC allows the optimiser to improve 'with_gc()'.
>
> What did I miss?

The fact that ~SomeObject() might be releasing a contentious resource
handle, of course.

Dave

Stephen Howe

unread,
Nov 21, 2004, 6:43:33 AM11/21/04
to
> This isn't going to happen, because the current C++ committee is
> totally uninterested in language safety.

I doubt whether that is even remotely true.

> Until some major changes are made in the committee, C++ will not become
> any
> safer, and we'll continue to have very unreliable software in that
> language.

I would be extremely interested to know what you would propose that would
make the language "safer".
I have a sneaking suspicion it would be "features" that would have the
drawback that we all have to accept that are executables are either slightly
slower or bigger.
Everybody would then have to swallow your opinion that the safety that
results, is worth paying for a slight loss in speed or bigger executables.
In reality, that is not even going to get off the ground.

If you can suggest "features" that make the langauge safer (for example C90
added function prototypes and C++ in its infancy added type-safe linkage),
_without_ having to give up any loss in speed or efficiency or bloated code,
then I am sure the committee will welcome your proposals with open arms.

Perhaps it be wise to think about the creators of C and creator of C++
achieved in that both languages are
such that executable code produced is pretty close to what the programmer
wrote without any excess run-time baggage.
That was part of the design criterion.
Any "safety" occurs mostly at compile-time, not at run-time.
That is not likely to change.

Stephen Howe

Peter Dimov

unread,
Nov 21, 2004, 6:15:46 PM11/21/04
to
mailto.anan...@gmail.com (Anand Hariharan) wrote in message
news:<22868227.04111...@posting.google.com>...
> pdi...@gmail.com (Peter Dimov) wrote in message news:<abefd130.04111...@posting.google.com>...
> > These two examples are not equivalent, so efficiency doesn't matter.
> > with_raii() executes SomeObject::SomeObject(), some_proc(),
> > SomeObject::~SomeObject(). with_gc() omits the last call.
> >
>
> Wasn't that /exactly/ Dave Harris' point? The two examples /can/ be
> compared since both envisage calling the default constructor of
> 'SomeObject' followed by a call to some_proc. 'with_raii()' does more
> (viz., calling the destructor of 'SomeObject') but without the
> overhead of GC. Dave further makes a case that an implementation that
> runs GC allows the optimiser to improve 'with_gc()'.
>
> What did I miss?

The side effects of ~SomeObject?

Peter Dimov

unread,
Nov 21, 2004, 6:16:08 PM11/21/04
to
bran...@cix.co.uk (Dave Harris) wrote in message
news:<memo.20041119205302.2064C@brangdon.m>...

> pdi...@gmail.com (Peter Dimov) wrote (abridged):
> > These two examples are not equivalent, so efficiency doesn't matter.
> > with_raii() executes SomeObject::SomeObject(), some_proc(),
> > SomeObject::~SomeObject(). with_gc() omits the last call.
>
> Yes. That's the point. We're considering the effect that difference in
> behaviour has on efficiency. With_gc() grants more freedom to the
> implementation and so is potentially more efficient.

No, with_gc doesn't grant more freedom to the implementation. It
simply does not execute ~SomeObject (and the implementation is not
free to invoke it), whereas with_raii does execute ~SomeObject (and
the implementation is not free to not invoke it). This is the only
difference between these two examples. with_raii is not required to
immediately deallocate the memory, if that's what you mean by with_gc
having more freedom.

> The .NET evangelist said "Deterministic de-construction is generally
> expensive...". He is not comparing one kind of deterministic
> deconstruction (eg try/catch) with another (eg RAII), he's comparing it
> with non-deterministic de-construction. So my example code is to the
> point.

Non-deterministic de-construction (if by that you mean finalization)
does not improve performance. Finalizers are evil and don't play well
with high-performance collectors. The only thing that does improve
performance is omitting de-construction.

> Although the examples are not equivalent, the first can substitute for the
> second (assuming no outstanding references), so it does make sense to
> compare their efficiencies.

No, it can't, because the behavior is different. The examples are only
equivalent if ~SomeObject has no observable side effects. If this is
the case, it can be optimized out and the examples become truly
equivalent and can generate the same code.

ka...@gabi-soft.fr

unread,
Nov 22, 2004, 4:33:36 PM11/22/04
to
al...@start.no (Alf P. Steinbach) wrote in message
news:<419e0b3d....@news.individual.net>...

They're linked by a third point: while garbage collection may be faster
than manual management of dynamically allocated objects, neither is as
fast as NO dynamic allocation -- of allocating objects on the stack, or
statically. (This is actually only partially true for large or complex
objects; if stack allocation causes you to make more copies, it may be
slower.) However, the real reason to insist on supporting stack based
and static objects isn't the performance win, but the fact that they
have different semantics, and that these semantics are very, very
useful. (Of course, the improved performance isn't a bad thing either.)

> There is another dimension here that is, I think, both more important
> and more directly relevant to the OP's question, "deterministic
> _destruction_" (the title of this thread), namely the difference
> between destructor calls and memory deallocation.

That's the basic difference between stack based objects and dynamically
allocated objects.

> I have no doubt that pure deallocation can both in principle and in
> practice be more efficiently done by a general garbage collector,
> simply because it's got a global view of things (memory, processor
> utilization), except in the few cases where we can use a specially
> crafted most efficient allocator such as a simple free-list / cache.

> I do doubt that there is any benefit from non-deterministic destructor
> calls, other than the dubious one of being able to turn off destructor
> calls on a per-object basis (possible in C#, and AFAIK done by default
> in Microsoft's Windows Forms GUI library for .NET).

That's pretty much my feeling as well, and IMHO, a garbage collector
could just ignore destructors. But I know that others disagree,
including some who know the problems a lot better than I do.

> The reason I doubt that there can be any benefit is two-fold: first of
> all, the calls will have to made anyway, and I fail to see how they
> can be faster when done by the garbage collector (other than that the
> garbage collector might choose to do the calls when there is otherwise
> low processor utilization); secondly, the kind of performance tweaking
> seemingly necessary for professional libraries like the one mentioned
> here means that not only do you not know when your destructor will be
> called, or if it ever will be called (if the application exits before
> the object is collected), you do not even know if there is a chance;
> so what possible benefit can automatically called destructor have in
> for example C# and Java (personal guideline: don't use it for
> anything!)?

> Is there anything in the C++ standard that prohibits operator delete
> from doing just the destructor call part, which I think it can do most
> efficiently and in a way that can be _relied_ on, and handing the
> memory deallocation bit over to a garbage collector which is better at
> that?

I don't think so. It's an interesting idea. But I suspect that much of
the advantage of garbage collection comes precisely from not having to
call most destructors at all; at least in my code, a fair percentage of
destructors are only concerned with memory management. (Note that it's
"I suspect". I'm really just guessing with regards to performance.)

> > The real argument for global and auto variables is thus, IMHO,
> > the additional behavior due to their different semantics. That
> > they are also faster than dynamic allocation is just icing on
> > the cake.

> I guess by "behavior", when it comes to auto variables you're
> referring to RAII. Well then, see last question above. Also, the D
> language allows classes to be declared 'auto', destructor call when
> object goes out of scope, and according to the documentation such
> objects are currently not allocated on the stack, i.e. they have the
> RAII semantics regarding destructor calls but not regarding memory
> reclamation.

Sort of like an std::auto_ptr with the logic you describe above?

IMHO, the real advantage of the RAII idiom, as it is usually used in
C++, is that the client does absolutely *nothing* to make it work. He
just defines an everyday local variable, and it just works.

Of course, the disadvantage is also that the client has no real control
over it. Like most people, I use RAII to manage mutex locks, and I've
had one or two cases (among several thousands -- they are exceptions)
where the scope of a local variable did NOT correspond to the time I
needed to hold the lock. Luckily, my mutex class still allows you to do
things the old way (which in turn allows creating new management
classes, with different ownership semantics).

I'm not sure; I don't really have enough experience to judge. What I do
feal sure about 1s 1) using local variables (as in C++) for
deterministic destructor calls IS more efficient than trying to achieve
deterministic destruction of dynamically allocated variables, and 2)
classical garbage collection (without deterministic destruction) is
cheaper than reference counting in general.

I'd guess that 99% of the time I need deterministic destruction, the
determination corresponds to the scope of a local variable. In the few
remaining cases, the number of copies of the object is very, very
limited, so I doubt that reference counting would be an objectional
expense.

--
James Kanze GABI Software http://www.gabi-soft.fr
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34

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

Dave Harris

unread,
Nov 22, 2004, 4:56:40 PM11/22/04
to
pdi...@gmail.com (Peter Dimov) wrote (abridged):
> No, with_gc doesn't grant more freedom to the implementation. It
> simply does not execute ~SomeObject (and the implementation is not
> free to invoke it), whereas with_raii does execute ~SomeObject (and
> the implementation is not free to not invoke it).

In with_gc, ~SomeObject will be executed by the garbage collector.

Remember we're not discussing C++ here, but some hypothetical mixture of
C# and C++ which has both garbage collection and RAII. Whether a C++ with
garbage collection should invoke destructors before reclamation is a
contentious point. I tend to think it shouldn't, but many people disagree
with me and I gather in C# it does.

The original poster's question wrote:
I asked why C# could not have simply incorporated
those semantics as a part of the language

so for this thread we have to build on C# as it is, not on C++ as it might
be.


> Non-deterministic de-construction (if by that you mean finalization)
> does not improve performance. Finalizers are evil and don't play well
> with high-performance collectors. The only thing that does improve
> performance is omitting de-construction.

I agree finalisers are evil. That isn't the point being discussed, though.
The code to call the finaliser exists once as part of the GC, and is not
replicated in each function which creates an object. That affects
efficiency.


> The examples are only equivalent if ~SomeObject has no observable
> side effects. If this is the case, it can be optimized out and
> the examples become truly equivalent and can generate the same code.

It can only be optimised out at a given call site if the implementation of
~SomeObject is known to the compiler at that call site. It may not be.

-- Dave Harris, Nottingham, UK

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

Herb Sutter

unread,
Nov 23, 2004, 2:01:12 PM11/23/04
to
On 16 Nov 2004 06:31:54 -0500, M Jared Finder <ja...@hpalace.com> wrote:

>Anand Hariharan wrote:
> > I recently attended a talk given by a .NET evangelist. Surprisingly,

Just curious, who was it?

> > the speaker was quite sincere and explained why Garbage collection is
> > no panacea (citing examples such as database connections and file
> > handles), taking his presentation through muddy waters of Dispose,
> > Close, etc.
> >
> > At one point he showed how C# chose to overload the "using" keyword in
> > a completely unrelated context viz., to specify that the variables
> > within a block defined by "using" should be destroyed as soon as they
> > leave the scope.
> >

> > At that point I asked why C# could not have simply incorporated those
> > semantics as a part of the language rather than requiring the
> > programmer explicitly request it at specific places. His response:

> > "Deterministic de-construction is generally expensive, especially if
> > one has several small objects sporadically sprewn all over."


> >
> > Is there a merit (statistical/empirical) to his assertion?

Short answer: No, but it's a common misconception even among experts.
Having deterministic destruction can incur a minor performance penalty,
and it is this he is thinking about. But deterministic destruction also
gains a significant practical performance advantage, and is otherwise
desirable. Further on this:

> > I thought
> > C++ went great lengths for RAII to be possible, eschewing runtime
> > guzzlers (such as mark-and-sweep) largely on performance grounds.
>

>This just seems crazy. I can't see how C#'s using, Java's try-finally,
>or C++'s automatic destruction would generate code that is different in
>any way.

Right, but it's useful to understand the actual issue. Here it is:

Let's say you have a stack frame containing one or more conceptually local
objects. If any such object should be cleaned up at the end of the
function (or more local scope), either for performance reasons or for
correctness reasons. Depending on the language you're using, you express
that essentially identically as one of the following:

- in C++, a stack-based object with a nontrivial destructor
- in C#, a using clause for a Disposable object
- in Java, the hand-coded Dispose pattern

In each case, you incur the overhead of an implicit or explicit
"try/finally" for the first local object that will need the cleanup -- and
it is that try/finally that the people who worry about performance are
talking about.

Note, however, that:

1. The constructs for expressing this that are essentially the same in all
languages; the only question is ease of use, and the winners there are
C++, C#, and Java, in that order. (To be complete, I should acknowlege
that there are other areas where C# and Java win on ease of use, but in
this particular case it is C++ that is the simpler language.)

2. Generally it's wrong NOT to write the deterministic destruction when
objects are conceptually local. If the object needs to be Dispose'd, you
need to Dispose it. So it's usually a red herring to say this incurs some
potential overhead, because to avoid the overhead would be to write an
incorrect program (and/or a less well-performing one, see below).

3. There are offsetting performance advantages to early destruction. In
particular, you incur a local try/finally for the first local variable in
a given scope that requires the cleanup (additional ones are essentially
free because you already have the try/finally in place), but you often get
great performance benefits later by reducing finalizer pressure and GC
work. (In one example I cite in talks, the microsoft.com website uses .NET
widely but at one point found that they were spending 70% of total system
time(!) in the GC. It wasn't .NET's fault or the GC's fault, but rather in
the way that GC was being used. The CLR performance team analyzed the
problem and told the app team to make one change: Before making a
server-to-server call, clean up (Dispose) all the objects you don't need
any more. With that one change, GC went down to 1%. I submit that the
problem would never have occurred if the app had been written in C++,
which uses deterministic destruction by default. C# and Java have it off
by default, and if you forget to write "using" or the Dispose pattern then
your code will still compile, but will have either a correctness bug or a
latent performance problem.)

Otherwise, if none of the conceptually local object does not require
cleanup, you express that essentially identically as one of the following:

- in C++, a stack-based object with a trivial destructor
(or, a heap-based object)
- in C#, no using clause
- in Java, no Dispose pattern

In each case, you avoid adding the exeption handling to do the cleanup.
Again, it's the same in all language. C++ happens to do turn cleanup on by
default for stack-based objects and does this optimization to
automatically avoid the overhead when the cleanup work is trivial.

So the argument really doesn't boil down to what some people often say,
namely whether deterministic destruction of conceptually local objects is
a good thing or not -- clearly it is important, otherwise you wouldn't
have C++ auto semantics, C# using statements, and Java Dispose patterns!
The argument really boils down to this: When you do need deterministic
destruction, you really do need it regardless of the language you're
using, and to avoid the overhead would be to write an incorrect program
(and often one with more overhead in other places).

>I can see there being problems with an old ABI that requires
>each function to register itself as having a cleanup step, but standards
>can't prevent all stupid implementations. In addition, using garbage
>collection will remove much of the work done in destructors since a most
>of the resources used in a program tends to be memory.

The latter is true for trivial destructors. In short, finalizers (often
but incorrectly called "destructors that run at GC time" which they are
NOT) are fundamentally flawed and extremely complex. (See for example
http://blogs.msdn.com/cbrumme/archive/2004/02/20/77460.aspx.)

I have personally come to the conclusion that destructors and GC are
completely separate and must be kept completely separate. Trying to
conflate the two ideas is the root of most of the problems with current GC
systems in my opinion; in particular, this manifestes most notably in the
case of finalizers which exactly try to tie those two things together, and
in the fact that all major current GC systems attempt to do GC instead of
destructors, rather than in addition to destructors (with the notable
exception of C++/CLI).

Of course, C++/CLI exposes what the CLI does (including finalizers) and
what C++ does (destructors) and by bringing them together shows how
beneficial destructors are even for today's GC systems. I think that
C++/CLI is the best it can be in this regards and is really compelling
over the current alternatives. I also think this approach could be taken
further and further improved upon; I have definite ideas, not ready for
publication, on potential improvements in GC by removing finalizers
outright (which could be viewed as somewhat radical and I agree that
departing from longtime practice is something that should never be done
lightly).

>I'd be interested in what Herb Sutter had to say about this, considering
>that one of the big advantages of C++/CLI over C# is the automatic
>calling of destructors.

Yes. Of course, C# has other advantages; I drool over anonymous delegates
(a restricted but very useful form of lambda functions / closures). It
would be cool to have those in C++... but that's another release...

Herb

---
Herb Sutter (www.gotw.ca) (www.pluralsight.com/blogs/hsutter)

Convener, ISO WG21 (C++ standards committee) (www.gotw.ca/iso)
Contributing editor, C/C++ Users Journal (www.gotw.ca/cuj)
Architect, Developer Division, Microsoft (www.gotw.ca/microsoft)

Alf P. Steinbach

unread,
Nov 24, 2004, 4:28:26 AM11/24/04
to
* Herb Sutter:

>
> I have personally come to the conclusion that destructors and GC are
> completely separate and must be kept completely separate. Trying to
> conflate the two ideas is the root of most of the problems with current GC
> systems in my opinion;

Very agreed.

What is the chance of e.g. Microsoft (or some) implementing the idea I
sketched earlier in this thread, of having C++ operator 'delete' just
call destructors and pass the memory deallocation work over to GC?

If someone could just do GC _right_ -- GC, and not all kinds of other
stuff -- then by default we'd get programs that would be both safer
and more efficient, instead of today's less safe and less efficient.


> Yes. Of course, C# has other advantages; I drool over anonymous delegates
> (a restricted but very useful form of lambda functions / closures). It
> would be cool to have those in C++... but that's another release...

Very disagreed. Nothing is as bad as canned functionality. Classes
that are implemented by the compiler, can only be implemented by the
compiler, and can't be extended, are as canned as they can be, VB style.

I'd rather have the corresponding and much more general Java concept in
C++ -- and for that matter, in C#.

But I think C++ is large enough as is... ;-)

--
A: Because it messes up the order in which people normally read text.
Q: Why is it such a bad thing?
A: Top-posting.
Q: What is the most annoying thing on usenet and in e-mail?

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

Alexander Terekhov

unread,
Nov 24, 2004, 4:26:26 AM11/24/04
to

David Abrahams wrote:
[...]

> > For the most part, RAII also works well in multi-threaded situations.
> > Most things aren't shared between threads, in well-designed apps.
>
> Also, I think with a lock-free memory allocator many of the
> performance advantages of GC disappear even for the multithreaded
> case.

With real GC you don't need to maintain shared counts.

See also:

http://www.hpl.hp.com/personal/Hans_Boehm/gc/example.html
(Expensive Explicit Deallocation: An Example)

One would still need "msync::ddhld" and "msync::ssb" for readers
and writers of atomic<section *> pointers respectively, though.

regards,
alexander.

Branimir Maksimovic

unread,
Nov 24, 2004, 4:34:29 AM11/24/04
to
> bran...@cix.co.uk (Dave Harris) wrote in message
> news:<memo.20041116215931.2492C@brangdon.m>...
>
> > A good GC implementation will not have any offsetting inefficiencies.
> > The allocation cost will be the same, and deallocation is basically
> > free - GC costs are mainly proportional to the number of live
> > objects. So it's a net win for GC (given that it is present and
> > running and the object is on the heap anyway).
>
> With a good relocating GC implementation, allocation cost will be much,
> much less than with manual memory management. Actual deallocation costs
> depend on different factors, and will often be higher than for manual
> management (you do have to relocate still live objects), but the total
> of both typically favors garbage collection. And the fact that the cost
> of deallocation occurs asynchronously will, in many applications, allow
> it to be shifted to dead time (e.g. while waiting for input), which
> makes it effectively free.
>
> Of course, I don't think that relocating GC is compatible with C++, as
> it now stands.

I think both of you are wrong. asynchronous GC will probably
work ok with single threaded applications but will choke
on multiple threads.
You cannot move live objects without locks;
That would result either in excessive memory allocation or
slowing down whole program.
Just imagine design that you have 2 threads that does actuall work
and one more that is responsible for memory allocation/deallocation,
which is controled by condition variable triggered by alloc/free
,syscalls, and perhaps timers... and that is basically garbage
collector.
allocs can be fast; just grab memory from already prepared available
pool
and give it away, but costs of reorganization/synchronization can be
high because there is no way that GC can know what other threads are
doing.
In some situations it would work perfect but in other, that can turn
4 CPU machine into one CPU machine :)

How's that better than (for example) this design.
There are memory pool of fixed allocators from strictest allignment
requirement up to some maximum value in different categories.
Category determines granularity of pool so each category is
represented
as vector of pointers to allocators; index of that vector determines
size of allocation and lower_bound of vector of vectors selects
category.
Maximum free memory that pool of allocators can hold is configurable.
Each thread has it's own independent allocator(actually pool of
allocs) and
memory blocks can be used interchangably between threads so one can
allocate other can free without locks.
Each allocator detects block that does not own and puts it in vector
of pointers called foreign blocks. When some configurable threshold
is reached thread puts free list of foreign blocks in global map
protected
by lock indexed by thread id.
When pool of available blocks
is exhausted allocator first takes foreign blocks, but if not
available
acquires lock for global map, then lookups for it's thread id and
checks
if free list is available. If avaliable it takes it.
If not, another allocator is used which is lock based and is
responsible for
maximum memory utilization(defragmentation and that things that
are done by GC).
So cost of allocation/deallocation is lower_bound over vector of eg 12
elements with single indexing into vector calculated by size.
So basically when application starts to allocate cost is high
but when some treshold is reached this flies like jet.
When testing against malloc from glibc we get lesser consumption
of memory( we have set of predefined block sizes which results in
overhead in each block but gets it's toll on lesser or no
fragmentation)
and significantly faster performance on multiple CPU's.
Compared to default allocator which is used in gcc's stdlib
it pushes and pops from completely sepparate lists in
10 different threads on single cpu 800% faster :)
When compared with __USE_MALLOC allocator it scales better
aproximately by the number of CPU's.

Greetings, Bane.

Andrew Browne

unread,
Nov 24, 2004, 2:33:30 PM11/24/04
to

"Herb Sutter" <hsu...@gotw.ca> wrote in message
news:3ht6q09j6gso7lank...@4ax.com...

> Let's say you have a stack frame containing one or more conceptually
> local
> objects. If any such object should be cleaned up at the end of the
> function (or more local scope), either for performance reasons or for
> correctness reasons. Depending on the language you're using, you
> express
> that essentially identically as one of the following:
>
> - in C++, a stack-based object with a nontrivial destructor

<snip>

> Otherwise, if none of the conceptually local object does not require
> cleanup, you express that essentially identically as one of the
> following:
>
> - in C++, a stack-based object with a trivial destructor
> (or, a heap-based object)

Unless I'm missing something, surely in many cases in C++/CLI, what
we're
actually going to be dealing with is something like the following

void F()
{
SomeType^ something = SomeLibraryFunctionReturningAHandle();

// .... some code ...
// we ought to clean up here if SomeType has a nontrivial destructor
}

We can't just do something like

void F()
{
SomeType something(*SomeLibraryFunctionReturningAHandle());

// .... some code ...
// automatic cleanup here
}

because SomeLibraryFunctionReturningAHandle() may return a handle to a
derived type, SomeType may well not have a copy constructor anyway, and
in
any case it would only be the copy that got cleaned up.

It seems to me that we really need something like this:-

void F()
{
smart_handle<SomeType>
something(SomeLibraryFunctionReturningAHandle());

// .... some code ...
// automatic cleanup here
}

and that, just as in Standard C++ we are encouraged to "avoid using bald
pointers" (e.g. Sutter in August 2004 CUJ), we should avoid using bald
handles in C++/CLI and use some smart handle equivalent of
std::auto_ptr or
tr1::shared_ptr. It seems to me that the equivalent of tr1::shared_ptr
would
have to use reference counting. (It could be argued that it could be
specialised to not use reference counting where it could be determined
at
compile time that its template argument T had a trivial destructor -
using
something like tr1::is_base_of<System::IDisposable, T> - but T might
simply
be a base type such as System::Object.) So it looks like we'd have the
performance overhead of smart pointers and garbage collection has
gained us
nothing.

BTW This isn't meant to be an attack on C++/CLI, which is a development
I'm
very interested in. I'd be very happy to find out that I'm mistaken in
my
interpretation above!

Andrew Browne

ka...@gabi-soft.fr

unread,
Nov 24, 2004, 9:09:56 PM11/24/04
to
Herb Sutter <hsu...@gotw.ca> wrote in message
news:<3ht6q09j6gso7lank...@4ax.com>...
[...]

> Let's say you have a stack frame containing one or more conceptually
> local objects. If any such object should be cleaned up at the end of
> the function (or more local scope), either for performance reasons or
> for correctness reasons. Depending on the language you're using, you
> express that essentially identically as one of the following:

> - in C++, a stack-based object with a nontrivial destructor
> - in C#, a using clause for a Disposable object
> - in Java, the hand-coded Dispose pattern

> In each case, you incur the overhead of an implicit or explicit
> "try/finally" for the first local object that will need the cleanup --
> and it is that try/finally that the people who worry about performance
> are talking about.

Doesn't this depend on the implementation. In C++, the cost of the
"try/finally" is essentially 0 in the implementations I'm familiar
with. Presumably, the try/finally in Java could use a similar
technique, but from what I've seen, they don't.

There is another essential difference: in C++, you have a separate
instance of this block, more or less, for each object which has a
non-trivial destructor. In Java, from what I have seen, it is usual to
only have one, even when several objects are concerned.

Finally, it is important to realize what is meant by "non trivial
destructor" in C++. There are two very large classes of objects which
have non trivial destructors in C++, but would not have them in Java:
- objects which use dynamic memory, and have to free it, and
- base class objects, which will typically have a virtual destructor,
which means a user defined destructor, which means a non-trivial
destructor, even if there is absolutely nothing to do.
Or course, it will probably be fairly rare for an object of the second
category to be on stack. On the other hand, it probably won't be that
rare for the on stack objects in C++ to be smart pointers, whose
destruction will trigger the destruction of another object. Ad
infinitum.

I think that even trying to compare a value oriented language with
manual memory management (like C++) to an everything is a dynamic object
with garbage collection language in this regard is a bit like comparing
apples to oranges. There are so many differences, starting with the
fact that you write programs in a different manner, that about the only
real thing you can do is to take two different implementations of the
same program, or the same program specifications, and compare them. But
even then, you are comparing specific implementations of the language.

> Note, however, that:

> 1. The constructs for expressing this that are essentially the same in
> all languages; the only question is ease of use, and the winners there
> are C++, C#, and Java, in that order. (To be complete, I should
> acknowlege that there are other areas where C# and Java win on ease of
> use, but in this particular case it is C++ that is the simpler
> language.)

There is also a question of robustness. I guess it is sort of linked to
ease of use by the client of the class in question, but the fact is that
if I provide my class with a non-trivial destructor in C++, there is no
way the client code can forget to call it. In Java, it is very easy --
in fact, usual in the Java code I've seen -- to forget to set up a
try/finally block. I'm not familiar with C#, but from what I gather
from this discussion, while it is much easier for the client to set up
the necessary mechanism than it would be in Java, it is still up to the
client to do so.

The critical argument for the C++ idiom (in this particular situation)
is that the client doesn't have to do anything. He gets the clean up
code automatically; in fact, he cannot avoid it.

> 2. Generally it's wrong NOT to write the deterministic destruction
> when objects are conceptually local.

I'm not sure I understand this point. In Java, for example, almost all
of my instances of StringBuffer, and a lot of instances of String, were
conceptually local, i.e. they were used locally for a single job, and
then forgotten. I never used deterministic destruction with them,
however, and I don't think that it was wrong not to.

Globally, I'd say that this is probably true for most value oriented
objects. Of course, in C++, a lot of value oriented objects don't have
a non-trivial destructor; those that do normally only have one for
memory management purposes. So these are objects which wouldn't use the
Dispose pattern in Java. But they are objects which are conceptually
local, unless you mean something else by conceptually local.

> If the object needs to be Dispose'd, you need to Dispose it. So it's
> usually a red herring to say this incurs some potential overhead,
> because to avoid the overhead would be to write an incorrect program
> (and/or a less well-performing one, see below).

Agreed. If the object needs to be Dispose'd, then the time needed to
set up the try/finally block will normally be negligible compared to the
time it needs in the dispose method, as well.

Of course, the real difference between C++ and these other languages is
that a lot more objects in C++ need to be Dispose'd, since this is how
we manage memory.

Again, with the compiler I use, there is NO exception handling cleanup
code which is executed in the normal path.

> Again, it's the same in all language. C++ happens to do turn cleanup
> on by default for stack-based objects and does this optimization to
> automatically avoid the overhead when the cleanup work is trivial.

The only optimization is not calling the non-existant destructor. I
don't call that an optimization.

> So the argument really doesn't boil down to what some people often
> say, namely whether deterministic destruction of conceptually local
> objects is a good thing or not -- clearly it is important, otherwise
> you wouldn't have C++ auto semantics, C# using statements, and Java
> Dispose patterns! The argument really boils down to this: When you do
> need deterministic destruction, you really do need it regardless of
> the language you're using, and to avoid the overhead would be to write
> an incorrect program (and often one with more overhead in other
> places).

> >I can see there being problems with an old ABI that requires each
> >function to register itself as having a cleanup step, but standards
> >can't prevent all stupid implementations. In addition, using garbage
> >collection will remove much of the work done in destructors since a
> >most of the resources used in a program tends to be memory.

> The latter is true for trivial destructors.

In C++, a destructor which only does deletes is not trivial. In C# or
in Java, it would not require deterministic disposal -- in fact, one
could argue that it doesn't require it in C++ either, except that
deterministic disposal is the only type of disposal we have.

> In short, finalizers (often but incorrectly called "destructors that
> run at GC time" which they are NOT) are fundamentally flawed and
> extremely complex. (See for example
> http://blogs.msdn.com/cbrumme/archive/2004/02/20/77460.aspx.)

> I have personally come to the conclusion that destructors and GC are
> completely separate and must be kept completely separate.

This is something I've felt for a long time myself as well. On the
other hand, some of the people who know the issues far better than I do
seem to think that calling destructors during garbage collection can be
important, so I'm not sure.

--
James Kanze GABI Software http://www.gabi-soft.fr
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34

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

Herb Sutter

unread,
Nov 25, 2004, 3:50:28 AM11/25/04
to
On 24 Nov 2004 14:33:30 -0500, "Andrew Browne"

<clcppm...@this.is.invalid> wrote:
>It seems to me that we really need something like this:-
>
>void F()
>{
> smart_handle<SomeType>
>something(SomeLibraryFunctionReturningAHandle());
>
> // .... some code ...
> // automatic cleanup here
>}

Yes, that's part of the product. It's spelled "auto_handle" which we might
shorted to "auto_hnd".

>and that, just as in Standard C++ we are encouraged to "avoid using bald
>pointers" (e.g. Sutter in August 2004 CUJ), we should avoid using bald
>handles in C++/CLI and use some smart handle equivalent

That's fine in that example. Getting back to the original examples,
stack-allocated automatic objects are useful critters that we use all the
time in C++. Now they work on CLI types too. That's all.

Basically, you are demonstrating nicely that all the C++ techniques and
idioms we're used to with native types really do apply evenly across the
type system also to CLI types. :-) I think that's a good thing.

Herb

Convener, ISO WG21 (C++ standards committee) (www.gotw.ca/iso)
Contributing editor, C/C++ Users Journal (www.gotw.ca/cuj)
Architect, Developer Division, Microsoft (www.gotw.ca/microsoft)

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

Emil Dotchevski

unread,
Nov 25, 2004, 3:49:03 AM11/25/04
to
> The performance problem is real, but optimizable.
> For C++, it's an artifact of trying to retrofit reference
> counts via templates.
> Optimizers need to know about reference counts. Reference
> count updates can potentially be hoisted out of innner loops
> using standard optimization techniques, if the compiler knows
> something about reference counts.

However note that since shared_ptr and weak_ptr are now TR1, at some
point in the future they will be part of the C++ standard. As such,
the implementation is free to optimize code based on their
standardized semantics without having to parse or understand any C++
source code that may be defining them. In other words, optimizers
would be free to "know about" reference counts.

(Then again, if compilers start to optimize based on standardized
semantics, shared_ptr and weak_ptr may not necessarily be the best
candidates for this. It would probably be more beneficial to start off
with std::vector instead, I think.)

> If you had optimization of reference counts and subscript
> checking at compile time, you could have the safety of Perl
> and Python with the speed of C/C++.

Subscript checking is a matter of implementation. A conforming C++
compiler could be checking array boundaries at run-time, including in
the case when the arrays are accessed through pointers. Also,
virtually all current implementations do range checking on all STL
containers and iterators, in debug mode.

> This isn't going to happen, because the current C++ committee is
> totally uninterested in language safety. Until some major
> changes are made in the committee, C++ will not become any
> safer, and we'll continue to have very unreliable software
> in that language.

Perhaps you mean to say that they are resisting core language changes
when standard library changes would suffice.

A core language change like the one you suggest would break a lot of
legacy code. Such code may be "unsafe", but if it is working you would
have hard time convincing its maintainers that they should throw it
away.

--Emil

Gerhard Wesp

unread,
Nov 26, 2004, 8:03:46 AM11/26/04
to
Emil Dotchevski <em...@collectivestudios.com> wrote:
> the case when the arrays are accessed through pointers. Also,
> virtually all current implementations do range checking on all STL
> containers and iterators, in debug mode.

They do?!

I just checked a very widespread open-source implementation, and it
definitely doesn't. I don't know why. I was always asking myself why
the h*** isn't there a simple assert( i < size() ) in
std::vector<>::operator[]( size_type i ). This would already catch *a
lot* of trivial errors and has no performance penalty for release
builds.

Cheers
-Gerhard
--
Gerhard Wesp o o Tel.: +41 (0) 43 5347636
Bachtobelstrasse 56 | http://www.cosy.sbg.ac.at/~gwesp/
CH-8045 Zuerich \_/ See homepage for email address!

Branimir Maksimovic

unread,
Nov 26, 2004, 11:26:50 AM11/26/04
to
ka...@gabi-soft.fr wrote in message
news:<d6652001.04112...@posting.google.com>...

>
> In C++, a destructor which only does deletes is not trivial. In C# or
> in Java, it would not require deterministic disposal -- in fact, one
> could argue that it doesn't require it in C++ either, except that
> deterministic disposal is the only type of disposal we have.
>

Actually, this can be done in c++ either, providing that every class
is derived from same single base class like Object;
I guess C# and Java references are not simple pointers, they are
objects
that interact with memory manager. So, if such design is needed
something like shared_ptr that does not deletes, rather calls
something like
MemManager::instance().dispose(Object*p) instead of delete, can just
notify
manager which runs in other thread that object does not have any
references left and that manager is free to delete it at appropriate
time (for example when pool off available memory blocks is exhausted)
, but I would leave reference counting job to shared_ptr.

Greetings, Bane.

Herb Sutter

unread,
Nov 26, 2004, 6:51:12 PM11/26/04
to
On 24 Nov 2004 21:09:56 -0500, ka...@gabi-soft.fr wrote:
>> - in C++, a stack-based object with a nontrivial destructor
>> - in C#, a using clause for a Disposable object
>> - in Java, the hand-coded Dispose pattern
>
>> In each case, you incur the overhead of an implicit or explicit
>> "try/finally" for the first local object that will need the cleanup --
>> and it is that try/finally that the people who worry about performance
>> are talking about.
>
>Doesn't this depend on the implementation. In C++, the cost of the
>"try/finally" is essentially 0 in the implementations I'm familiar
>with. Presumably, the try/finally in Java could use a similar
>technique, but from what I've seen, they don't.

I was focusing on the fact that there is a try/finally which is present in
all cases. Separate from that is that there are different EH
implementations with different costs for that try block (VC++ currently
uses both of the popular approaches, and which one is generated depends on
the processor target).

>There is another essential difference: in C++, you have a separate
>instance of this block, more or less, for each object which has a
>non-trivial destructor. In Java, from what I have seen, it is usual to
>only have one, even when several objects are concerned.

Some of that is up to the implementation. At the source code level you can
and do get the same effect with the Java Dispose pattern and C# "using"
with what is the equivalent of nested blocks in C++ (i.e., variables with
shorter/nested lifetimes because of being in shorter/inner blocks).

>I think that even trying to compare a value oriented language with
>manual memory management (like C++) to an everything is a dynamic object
>with garbage collection language in this regard is a bit like comparing
>apples to oranges.

That is at the heart of it, yes, but the more I work in this area the
fewer truly fundamental (i.e., not solvable) differences I find.

For the memory management point: It's probably only a matter of time until
we have standardized GC support for the native heap (which is already
optional) which removes one of the two differences.

For the value oriented language point: Yes, there's a basic difference
between value and reference types -- and C++ already has both of them,
which is what makes it easier for C++ to be extended to platforms like CLI
that mostly only have the latter.

>There is also a question of robustness. I guess it is sort of linked to
>ease of use by the client of the class in question, but the fact is that
>if I provide my class with a non-trivial destructor in C++, there is no
>way the client code can forget to call it.

Yes, I was trying to be kind by only calling that "ease of use" here. :-)
I did in another place go further and point out that C++ is all about
"correctness by default," and point #2 was directed at this.

>In Java, it is very easy --
>in fact, usual in the Java code I've seen -- to forget to set up a
>try/finally block. I'm not familiar with C#, but from what I gather
>from this discussion, while it is much easier for the client to set up
>the necessary mechanism than it would be in Java, it is still up to the
>client to do so.
>
>The critical argument for the C++ idiom (in this particular situation)
>is that the client doesn't have to do anything. He gets the clean up
>code automatically;

Bingo.

>in fact, he cannot avoid it.

Other than by using the heap, of course. Or when the "local" object is
returned from another function, such as a factory, and has to be held by a
smart pointer.

>Of course, the real difference between C++ and these other languages is
>that a lot more objects in C++ need to be Dispose'd, since this is how
>we manage memory.

It turns out that a lot more objects in Java and CLI and C# need to be
Dispose'd than the designers of those environments originally seem to have
thought.

>> In short, finalizers (often but incorrectly called "destructors that
>> run at GC time" which they are NOT) are fundamentally flawed and
>> extremely complex. (See for example
>> http://blogs.msdn.com/cbrumme/archive/2004/02/20/77460.aspx.)
>
>> I have personally come to the conclusion that destructors and GC are
>> completely separate and must be kept completely separate.
>
>This is something I've felt for a long time myself as well. On the
>other hand, some of the people who know the issues far better than I do
>seem to think that calling destructors during garbage collection can be
>important, so I'm not sure.

No, it really is worse than that: It is fundamentally impossible in the
general case to correctly call _destructors_ during GC.

That is why people invented the separate concept of _finalizers_, which do
run at GC time, but finalizers really are not destructors, and they can
only do a subset of the operations that a destructor can do. For example,
finalization has to be unordered (because of cycles) and so in your
finalizer you can never reliably touch another finalizable object because
it might already have been torn down. Clearly many destructors can and do
touch other objects, and so it is impossible to call such destructors
correctly at finalization time. See the blog above for more about the
limitations of finalizers.

The main reason people (including GC experts) believe that running
finalizers at GC time is important is because pretty much all major GC
systems that have ever existed do GC _instead of_ destructors. Absent
destructors, you need to have a last chance to tear key objects down
somehow, and finalizers are that last-chance patch. My belief is that in a
language that has GC _in addition to_ destructors, you probably don't need
or want finalizers at all. Finalizers are extremely problematic, as noted
in the blog above.

Herb

Convener, ISO WG21 (C++ standards committee) (www.gotw.ca/iso)
Contributing editor, C/C++ Users Journal (www.gotw.ca/cuj)
Architect, Developer Division, Microsoft (www.gotw.ca/microsoft)

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

Herb Sutter

unread,
Nov 28, 2004, 6:38:33 AM11/28/04
to
On 26 Nov 2004 18:51:12 -0500, Herb Sutter <hsu...@gotw.ca> wrote:
>That is why people invented the separate concept of _finalizers_, which do
>run at GC time, but finalizers really are not destructors, and they can
>only do a subset of the operations that a destructor can do. For example,
>finalization has to be unordered (because of cycles) and so in your
>finalizer you can never reliably touch another finalizable object because
>it might already have been torn down.

To this I should also add that you can invest effort to arrange things in
such a way that you can be sure that another finalizable object you want
to use hasn't been finalized already when your own finalizer runs, but
such designs typically require handshaking and are very brittle. Not
recommended.

John Nagle

unread,
Nov 29, 2004, 6:02:05 AM11/29/04
to
Gerhard Wesp wrote:
> Emil Dotchevski <em...@collectivestudios.com> wrote:
> > the case when the arrays are accessed through pointers. Also,
> > virtually all current implementations do range checking on all STL
> > containers and iterators, in debug mode.
>
> They do?!

Few STL implementations have serious checking. STLport in debug
mode has strong checking, with iterator validation. But it's rare.


>
> I just checked a very widespread open-source implementation, and it
> definitely doesn't. I don't know why. I was always asking myself why
> the h*** isn't there a simple assert( i < size() ) in
> std::vector<>::operator[]( size_type i ).

That's by design. It's for "consistency with built-in arrays"
and "performance".

(Of course, if C++ compilers knew more about containers, most
subscript checks could be hoisted out of loops, as they were in
some advanced Pascal compilers. But that technology has been
lost to history.)

John Nagle
Animats

Howard Hinnant

unread,
Nov 29, 2004, 4:45:56 PM11/29/04
to
In article <05wqd.26247$zx1....@newssvr13.news.prodigy.com>,
John Nagle <na...@animats.com> wrote:

> Gerhard Wesp wrote:
> > Emil Dotchevski <em...@collectivestudios.com> wrote:
> > > the case when the arrays are accessed through pointers. Also,
> > > virtually all current implementations do range checking on all STL
> > > containers and iterators, in debug mode.
> >
> > They do?!
>
> Few STL implementations have serious checking. STLport in debug
> mode has strong checking, with iterator validation. But it's rare.

Your information is out of date by a couple of years. Most
implementations do have such a debug mode. I can personally speak for
Metrowerks.

-Howard

Seungbeom Kim

unread,
Nov 29, 2004, 4:52:43 PM11/29/04
to
John Nagle wrote:

> Gerhard Wesp wrote:
>
>>I just checked a very widespread open-source implementation, and it
>>definitely doesn't. I don't know why. I was always asking myself why
>>the h*** isn't there a simple assert( i < size() ) in
>>std::vector<>::operator[]( size_type i ).
>
> That's by design. It's for "consistency with built-in arrays"
> and "performance".

With respect to performance, asserts simply become null statements in
release builds, so does it matter? Besides, separate libraries for debug
builds and release builds are not necessary since all the definitions
are in the header files, in most cases. I think the benefit of catching
the errors outweighs the loss of performance in debug builds.

(Well, it could be argued that someone would be sure of having no
out-of-range errors but still want asserts in other cases and want to
leave the asserts in release builds..)

With respect to consistency, no matter whether it is an built-in array
or a vector, as soon as you access an out-of-range element the behaviour
is undefined, so it doesn't matter whether the program continues or
aborts. A sensible program should not depend on what would happen when
it accessed an out-of-range element.

--
Seungbeom Kim

Gerhard Wesp

unread,
Nov 30, 2004, 5:01:10 PM11/30/04
to
Seungbeom Kim <musi...@bawi.org> wrote:
> (Well, it could be argued that someone would be sure of having no
> out-of-range errors but still want asserts in other cases and want to
> leave the asserts in release builds..)

Then it's easy to define one's own assert() macro a la
``always_assert()''.

Cheers
-Gerhard
--
Gerhard Wesp o o Tel.: +41 (0) 43 5347636
Bachtobelstrasse 56 | http://www.cosy.sbg.ac.at/~gwesp/
CH-8045 Zuerich \_/ See homepage for email address!

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

ka...@gabi-soft.fr

unread,
Nov 30, 2004, 6:33:23 PM11/30/04
to
Seungbeom Kim <musi...@bawi.org> wrote in message
news:<cog1ob$ldq$1...@news.Stanford.EDU>...
> John Nagle wrote:

> > Gerhard Wesp wrote:

> >>I just checked a very widespread open-source implementation, and it
> >>definitely doesn't. I don't know why. I was always asking myself
> >>why the h*** isn't there a simple assert( i < size() ) in
> >>std::vector<>::operator[]( size_type i ).

> > That's by design. It's for "consistency with built-in arrays"
> > and "performance".

> With respect to performance, asserts simply become null statements in
> release builds, so does it matter?

I've yet to deliver any code where asserts have simply become null
statements. Sounds strang to me, sort of like wearing a life jacket for
your lessons in port, and taking it off when you go to sea.

> Besides, separate libraries for debug builds and release builds are
> not necessary since all the definitions are in the header files, in
> most cases.

Which is formally a problem, since if I do define NDEBUG in one module,
but not in another, I have undefined behavior (violation of the
one-definition rule). Of course, I've never seen a compiler where this
is a problem, but if for some reason the compiler doesn't inline the
code (not likely for vector::operator[], but possible for more complex
functions), it's pretty much up in the air whether the function will
have the asserts or not.

> I think the benefit of catching the errors outweighs the loss of
> performance in debug builds.

> (Well, it could be argued that someone would be sure of having no
> out-of-range errors but still want asserts in other cases and want to
> leave the asserts in release builds..)

That's the usual pratice.

> With respect to consistency, no matter whether it is an built-in array
> or a vector, as soon as you access an out-of-range element the
> behaviour is undefined, so it doesn't matter whether the program
> continues or aborts. A sensible program should not depend on what
> would happen when it accessed an out-of-range element.

Certainly. On the other hand, it's far preferable to know immediatly
that the program is broken, rather than for it to continue, and give
wrong results.

--
James Kanze GABI Software http://www.gabi-soft.fr
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34

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

Gerhard Wesp

unread,
Dec 1, 2004, 5:13:59 PM12/1/04
to
ka...@gabi-soft.fr wrote:
> I've yet to deliver any code where asserts have simply become null
> statements. Sounds strang to me, sort of like wearing a life jacket for
> your lessons in port, and taking it off when you go to sea.

One might just as well compare it to a child learning to ride it's
bicycle, using support wheels. When enough confidence is built up, the
support wheels are thrown away and you can go much faster.

See my other posting with the always_assert() macro, though!

> Which is formally a problem, since if I do define NDEBUG in one module,
> but not in another, I have undefined behavior (violation of the

Hmm... Good point. Seems to necessitate separate debug and release
builds for all your dependent modules.

Cheers
-Gerhard
--
Gerhard Wesp o o Tel.: +41 (0) 43 5347636
Bachtobelstrasse 56 | http://www.cosy.sbg.ac.at/~gwesp/
CH-8045 Zuerich \_/ See homepage for email address!

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

Dave Harris

unread,
Dec 1, 2004, 5:59:03 PM12/1/04
to
hin...@metrowerks.com (Howard Hinnant) wrote (abridged):

> > Few STL implementations have serious checking. STLport in debug
> > mode has strong checking, with iterator validation. But it's rare.
>
> Your information is out of date by a couple of years. Most
> implementations do have such a debug mode. I can personally speak for
> Metrowerks.

Microsoft's VC++ 7.1 doesn't have such a debug mode. I believe 7.1 is
still their current release. I suspect they account for "most"
implementations, at least in the Windows world.

-- Dave Harris, Nottingham, UK

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

ka...@gabi-soft.fr

unread,
Dec 1, 2004, 7:58:17 PM12/1/04
to
Herb Sutter <hsu...@gotw.ca> wrote in message
news:<r7req09anne3692hu...@4ax.com>...

> On 24 Nov 2004 21:09:56 -0500, ka...@gabi-soft.fr wrote:
> >> - in C++, a stack-based object with a nontrivial destructor
> >> - in C#, a using clause for a Disposable object
> >> - in Java, the hand-coded Dispose pattern

> >> In each case, you incur the overhead of an implicit or explici