Topic: Destructors of global objects considered harmful
Author: fjh@munta.cs.mu.OZ.AU (Fergus Henderson)
Date: Sat, 24 Sep 1994 06:53:15 GMT Raw View
nessus@mit.edu (Douglas Alan) writes:
>fjh@munta.cs.mu.OZ.AU (Fergus Henderson) writes:
>[some lost attributions]
>> > Okay, I'm convinced. But then the C++ standard should allow me to
>> > redefine ::operator delete() in a portable manner and in such a way
>> > that my user-defined ::operator delete() can use the standard
>> > ::operator delete().
>
>> Why? What's wrong with just using malloc() and free()?
>> In the C++ implementations I've seen, that's what ::operator new() and
>> ::operator delete() do anyway.
>
>By this argument why have a C++ standard at all? Different C++
>implementations let me do more or less the same things in more or less
>the same ways.
That's not my argument. My argument is just that malloc() and free()
are sufficient - anything that can be done with the standard ::operator
new() and ::operator delete() can be done with malloc() and free().
The fact that the standard ::operator new/delete *can* be implemented
using malloc/free is proof of that - how they actually *are* implemented
doesn't make any difference to the argument.
In other words, what I am saying is that using malloc() and free() in
your redefinition of ::operator new and ::operator delete will let you
do *exactly* the same things that calling the original standard
::operator new and ::operator delete would let you do (plus it would
let you do a few other things as well). The proposed extension would
provide no additional functionality, it would just provide more ways
of doing the same things.
--
Fergus Henderson - fjh@munta.cs.mu.oz.au
Author: rfg@netcom.com (Ronald F. Guilmette)
Date: Sat, 24 Sep 1994 07:44:46 GMT Raw View
In article <NESSUS.94Sep13190159@twitch.mit.edu> nessus@mit.edu (Douglas Alan) writes:
>In article <KOHTALA.94Sep13234149@hardy.trs.ntc.nokia.com>
>kohtala@hardy.trs.ntc.nokia.com (Kohtala Marko) writes:
>
>> Why would you not want the destructors to be called?
>
>Perhaps you missed my original posting in this thread that explained
>why 99% of the time I don't want the destructor of a global object to
>be called. I'll briefly recap:
>
> (1) In 99 out of 100 cases a destructor just returns memory to the
> heap. This is at best a waste of time if the program is just
> going to terminate after all this is done anyway. In some
> programs with large global objects, this may actually
> take a long time.
Returning ``large'' object to the heap doesn't take very long. Returning
_many_ objects to the heap may, but you are making the same mistake that
all programmer have a tendency to make all the time, i.e. you are wasting
hours worrying about microseconds. This is a false economy.
I recently purified one medium-sized C program I had, and while doing
that I also found a lot of cases where (at program termination time)
a good deal of heap blocks were left allocated and (in some cases)
actually dangling (i.e. leaked). No matter, I put a bunch of calls
to free toward the end of my program and made them all #ifdef PURIFY.
I then pressed ahead with the business at hand... i.e. finding the
*real* memory leaks... a job for which Purify is eminently well suited.
You could do likewise in your C++ programs by having any destructors for
file-scope objects just return _to_ the heap any objects which the corres-
ponding CONstructors had allocated _from_ the heap. (You see, it's kind
of like a yin and yang. Try it for awhile. You may just like it.) And
of course, if you really don't want that termination overhead in the
program you actually ship, well... you just do as I did and put your
calls to `free' inside an #ifdef PURIFY section.
What's the big deal? Why all the bitching and moaning?
Do we have to change the language just because you want to fully exploit
the power of purify AND you refuse to write an #ifdef or two?
--
-- Ron Guilmette, Sunnyvale, CA ---------- RG Consulting -------------------
---- domain addr: rfg@netcom.com ----------- Purveyors of Compiler Test ----
---- uucp addr: ...!uunet!netcom!rfg ------- Suites and Bullet-Proof Shoes -
Author: fjh@munta.cs.mu.OZ.AU (Fergus Henderson)
Date: Mon, 19 Sep 1994 06:09:16 GMT Raw View
nessus@mit.edu (Douglas Alan) writes:
>dai@eskimo.com (Davidson Corry) writes:
>
>> You might also want to replace global ::operator delete() with a
>> version that turns into a no-op just before you drop out of the
>> main() function. (Dunno whether this requires you to rewrite the
>> runtime support library or not...)
>
>Okay, I'm convinced. But then the C++ standard should allow me to
>redefine ::operator delete() in a portable manner and in such a way
>that my user-defined ::operator delete() can use the standard
>::operator delete().
Why? What's wrong with just using malloc() and free()?
In the C++ implementations I've seen, that's what ::operator new() and
::operator delete() do anyway.
--
Fergus Henderson - fjh@munta.cs.mu.oz.au
Author: nessus@mit.edu (Douglas Alan)
Date: Mon, 19 Sep 1994 17:12:50 GMT Raw View
In article <9426216.7453@mulga.cs.mu.OZ.AU> fjh@munta.cs.mu.OZ.AU
(Fergus Henderson) writes:
> > Okay, I'm convinced. But then the C++ standard should allow me to
> > redefine ::operator delete() in a portable manner and in such a way
> > that my user-defined ::operator delete() can use the standard
> > ::operator delete().
> Why? What's wrong with just using malloc() and free()?
> In the C++ implementations I've seen, that's what ::operator new() and
> ::operator delete() do anyway.
By this argument why have a C++ standard at all? Different C++
implementations let me do more or less the same things in more or less
the same ways.
|>oug /\lan
<nessus@mit.edu>
Author: nessus@mit.edu (Douglas Alan)
Date: Fri, 16 Sep 1994 20:33:00 GMT Raw View
In article <358072$iun@eve.atm.com> dai@eskimo.com (Davidson Corry) writes:
> You might also want to replace global ::operator delete() with a
> version that turns into a no-op just before you drop out of the
> main() function. (Dunno whether this requires you to rewrite the
> runtime support library or not...)
Okay, I'm convinced. But then the C++ standard should allow me to
redefine ::operator delete() in a portable manner and in such a way
that my user-defined ::operator delete() can use the standard
::operator delete().
|>oug /\lan
<nessus@mit.edu>
Author: russgold@netaxs.com (Russell Gold)
Date: Tue, 13 Sep 1994 23:21:28 -0500 Raw View
In article <MCOOK.94Sep13130922@galaga.lna.logica.com>,
michaelc@lna.logica.com wrote:
> >>>>> "nessus" == Douglas Alan <nessus@mit.edu> writes:
>
> nessus> THEREFORE, I propose [...]
>
> Okay, but you're required to submit one of your livers with each proposal you
> make. That way, people will only make proposals for extensions they
> absolutely can't live without, and each person will be limited to two
> proposals.
Hey! Where did you get the second liver? I only have one... ;)
------------------------------------------------------------------------
Russell Gold | "... society is tradition and order
russgold@netaxs.com (preferred) | and reverence, not a series of cheap
russgold@aol.com | bargains between selfish interests."
russgold@boeing.com |
Author: dai@eskimo.com (Davidson Corry)
Date: 14 Sep 1994 23:15:46 GMT Raw View
>In article <TMB.94Sep12130931@arolla.idiap.ch> tmb@arolla.idiap.ch
>(Thomas M. Breuel) writes:
>
>>For the record, this is not a mistake Purify makes. It is a problem
>>with the way Douglas uses destructors. From his description, Purify
>>correctly reports that his program has a memory leak, since, at exit,
>>he has unreferenced memory. The same leaks would be reported by
>>Boehm's collector, for example.
In article <NESSUS.94Sep12135930@twitch.mit.edu>, nessus@mit.edu
(Douglas Alan) says:
>
>For the record, this not a problem with the way I use destructors. It
>is a problem with C++ that C++ won't allow me to specify how I would
>like the destructors to be used. If I could tell C++ that I don't
>want certain destructors to be called after the end of main(), then I
>wouldn't have a problem.
I can understand why you might not want to waste cycles destructing
objects which are about to become irrelevant anyway upon program
termination. Why clean your desk when they're about to knock down
the building?
But IMHO this _is_ a problem with the way you (want to) use destructors,
because that is not what destructors do. C++ goes to considerable
trouble to ensure that destructors are called automatically when an
object goes out of scope (which globals in some sense do when a program
terminates). A tool letting you suppress this behavior for (some)
globals is an extension to the language (not the correction of an
oversight in its design) and in my view not a terribly useful one.
Contrived example of why you might NOT want to suppress destruction
of globals:
At some point in the future, you or the person assigned to maintain
your program decide it would be useful/cool to instrument the
constructor and destructor for class X so that, when an X is
instantiated, "X built 9/14/94 6:42 p.m." gets copied to an
audit logfile, and a corresponding "X decomposed 9/14/94 9:12 p.m."
gets logged when the destructor is called.
If the destructors are suppressed for global X's at termination,
the "decompose" audits never get done. [YOU might be smart enough
to know today that no one tomorrow would ever want to do something
in an object's destructor whose effects will persist beyond
end-of-program. I'm not.]
Off-the-top-of-my-head suggestion for a kluge to avoid paying the
freight to destruct doomed global objects:
Make X an abstract base class, and derive GlobalX and LocalX from it.
LocalX looks just like X. GlobalX does too except that its destructor
function is empty. Use GlobalX objects at global scope and LocalX
objects otherwise.
[Bleahh. Too fancy. Make
static int DestructorDoesNothing = FALSE;
a member of X. Execute
X::DestructorDoesNothing = TRUE;
as the last statement in main(). Have X::~X() skip itself if
X::DestructorDoesNothing == TRUE.
Ugly much? Yeah, but it works. You could even use it to
perform the audit but skip everything else.]
You might also want to replace global ::operator delete() with a
version that turns into a no-op just before you drop out of the
main() function. (Dunno whether this requires you to rewrite the
runtime support library or not...)
Granted, this is a fair amount of dancing to do just to avoid stepping
on the toes of C++. But I think that's generally preferable to adding
a new language feature, particularly one of such limited application.
And, of course, nothing prevents you from lobbying some compiler vendor
to provide a command-line switch that does what you're asking for and
calling it an "optimization". <grin>
-- dai, exiting stage left, to the tune of "Eve of Destruction"...
Author: jaf3@ritz.cec.wustl.edu (John Andrew Fingerhut)
Date: 15 Sep 1994 13:02:00 -0500 Raw View
In article <NESSUS.94Sep13175759@twitch.mit.edu>,
Douglas Alan <nessus@mit.edu> wrote:
:
:
:I didn't write class RWTPtrOrderedVector, Rogue Wave Software
:Inc. did. Are you saying that I should write my own class that
:duplicates all the functionality of RWTPtrOrderedVector except that it
:has a slightly different destructor, just so that I can get some
:behavior that C++ should support to begin with? This does great
:things for the notion of reusable software....
:
:> So, there is nothing in C++ that keeps you from doing this either.
:
:There's nothing that keeps me from implementing my program on a Turing
:Machine. The question is how elegant, maintainable, and efficient my
:program will end up being.
:
:> If you want a quick-and-dirty solution, you can also send your self
:> a SIGKILL at the end of main or call _exit; for debugging at least,
:> that should be perfectly adequate.
:
:No, I want the correct solution -- which C++ doesn't provide me with
:the ability to implement. The solution I am using is the best
:solution that C++ provides, but it suffers in elegance,
:maintainability, and efficiency.
:
:|>oug /\lan
: <nessus@mit.edu>
I didn't get into this until I was pretty sure of my answer. I also use
RogueWave and I have had to deal with similar situations. I think that
RogueWave made a poor design decision in not providing the ability for
their collection classes to delete all of their memebers. They do have
member functions which do just that (clearAndDestroy). Because their
destructors are virtual, you can derive a class from them that calls the
clearAndDestroy member in its destructor (RogueWave's destructor simply
calls clear() which does not delete them members.) If you have to actually
iterate across the collection, you can do that too, inside the derived
classes destructor. This should accomplish what you want, without doing
"great things for the notion of reusable software..." or without extending
the language.
Stephen W. Gevers
sg3235@shelob.sbc.com
Author: fjh@munta.cs.mu.OZ.AU (Fergus Henderson)
Date: Thu, 15 Sep 1994 06:55:44 GMT Raw View
nessus@mit.edu (Douglas Alan) writes:
>Of course, there are existing workarounds for the problem I have
>outlined. The best one is probably to define 'objectCache' as a
>reference like this:
>
> Vector<Object*>& objectCache = *new Vector<Object*>;
>
>rather than as you normally would like this:
>
> Vector<Object*> objectCache;
>
>This solution is rather ugly, though, for something you want to do 99
>out of 100 times.
I disagree. Most of use would only want to use this 1 out of 100
times, or even less than that. I don't think there is any need for C++
to provide explicit support for this practice.
>Also, this workaround entails that every reference
>to objectCache will have to go through an unnecessary pointer
>dereference. There should be a better way.
OK, so use the following:
#include "no_destructor.h"
NO_DESTRUCTOR(Vector<Object *>, objectCache); // default constructor
NO_DESTRUCTOR(Vector<Object *>, objectCache) (100);
// use the Vector<Object *>::Vector(int) constructor
where "no_destructor.h" contains the following:
#include <generic.h> // for name2()
#include <new.h> // for placement operator new
template <class T>
struct RawStorage {
union {
char storage[sizeof(T)];
// ensure adequate alignment:
double align_d;
void * align_p;
long align_l;
// etc.
} buf;
};
#define NO_DESTRUCTOR(type, name) \
RawStorage< type > name2(storage_for_,name); \
type & name = \
(type &) name2(storage_for_,name); \
static void * name2(dummy_init_for_,name) = \
new (&name) type
so that
NO_DESTRUCTOR(Vector<Object *>, objectCache);
expands to
static RawStorage< Vector<Object *> > storage_for_objectCache;
Vector<Object *> & objectCache =
(Vector<Object *> &) storage_for_objectCache;
static void * dummy_init_for_objectCache =
new (&objectCache) Vector<Object *>;
I don't claim that this use of macros is elegant, but it does solve
your problem.
Any decent optimizing compiler would easily be able to replace all occurrences
of `objectCache' with `storage_for_objectCache' (cast to the appropriate type)
since the initializer for the reference is just the address of a static
variable, and would not need to allocate any memory for `objectCache' itself.
Also, the compiler doesn't need to allocate any memory for
`dummy_init_for_objectCache' since the variable isn't used, although
it must still evaluate the initializer for that variable.
So with a good optimizing compiler, this solution would be maximally
efficient - the storage for the objectCache would be allocated
statically and initialized at program startup, references to the
objectCache would not require any indirectino, but as desired
the objectCache would not be destroyed at the end of the program.
Of course, there probably aren't any good optimizing C++ compilers
around yet :-(
--
Fergus Henderson - fjh@munta.cs.mu.oz.au
Author: mwt@lightside.com (Mark Thompson)
Date: 12 Sep 1994 01:46:06 GMT Raw View
In article <NESSUS.94Sep8195051@twitch.mit.edu>, nessus@mit.edu (Douglas Alan) says:
>
The problem I have encountered is in conjunction with
>the debugging tool Purify.
>
>Purify detects memory leaks by running a garbage collector immediately
>before the program terminates. If Purify finds any unreferenced
>objects, then these are reported as leaks. This is superior to most
>memory leak detectors, which just report all memory that had not been
>returned to the heap when the program exited. This latter method is
>inferior because it will incorrectly report as leaks objects that are
>referenced by global objects.
>
>How does Purify interact with C++? Let's think about a test case.
>Let's say our program has a global variable called 'objectCache'.
>Furthermore suppose that 'objectCache' is of type 'Vector<Object*>'.
>What happens now when our program terminates? After main() is done,
>'objectCache's destructor is executed, which then (needlessly) deletes
>the 'Object**' array that holds all the Object pointers in the vector.
>But 'Vector<Object*>::~Vector()' does not delete the myriad Object
>pointers that were in the vector. After the destructor finishes
>running, Purify's garbage collector kicks in and finds all the Objects
>that used to be pointed to by 'objectCache'. Since the vector has
>been destroyed, none of these Objects are now referenced by anything,
>and Purify thinks that it has discovered many memory leaks.
>
>In fact, Purify has not found a true memory leak, and Purify would
>have worked correctly, and the program would have run faster if that
>pesky destructor hadn't been called.
Whether or not you want destructors called for Global objects after main
is something that can be argued. It just seems that you are arguing
that since a debugging tool makes mistakes, the standard shouldn't
have it. I like the idea of destructors running because the purpose of
destructors is to return things back to normal. By not doing this, files
will be left open and who knows what else bad can happen.
Maybe the company should make Purify to not find leaks where they don't
exist. The purpose of debuggers is to report the truth about what is
happening in a program. If the debugger lies, then it's not doing its
job.
Mark Thompson
-=-=-=-=-=-=-
mwt@lightside.com
West Covina, California
Author: tmb@arolla.idiap.ch (Thomas M. Breuel)
Date: 12 Sep 1994 11:09:29 GMT Raw View
In article <350bsu$27d@covina.lightside.com> mwt@lightside.com (Mark Thompson) writes:
|In article <NESSUS.94Sep8195051@twitch.mit.edu>, nessus@mit.edu (Douglas Alan) says:
|>How does Purify interact with C++? Let's think about a test case.
|>Let's say our program has a global variable called 'objectCache'. [...]
|
|Whether or not you want destructors called for Global objects after main
|is something that can be argued. It just seems that you are arguing
|that since a debugging tool makes mistakes, the standard shouldn't
|have it.
|[...]
|Maybe the company should make Purify to not find leaks where they don't
|exist. The purpose of debuggers is to report the truth about what is
|happening in a program. If the debugger lies, then it's not doing its [job]
For the record, this is not a mistake Purify makes. It is a problem
with the way Douglas uses destructors. From his description, Purify
correctly reports that his program has a memory leak, since, at exit,
he has unreferenced memory. The same leaks would be reported by
Boehm's collector, for example.
Thomas.
Author: nessus@mit.edu (Douglas Alan)
Date: Mon, 12 Sep 1994 17:50:55 GMT Raw View
In article <350bsu$27d@covina.lightside.com> mwt@lightside.com (Mark
Thompson) writes:
> Whether or not you want destructors called for Global objects after main
> is something that can be argued.
Exactly. Which is why C++ should have a better mechanism than it does
for allowing a programmer to specify on an object-by-object basis
which global objects should have their destructors invoked after the
end of main().
> It just seems that you are arguing that since a debugging tool makes
> mistakes, the standard shouldn't have it.
No, I'm saying that the programming language should give the
programmer control of what is happening. If I don't want certain
destructors to be called, I should be able to easily say so.
> Maybe the company should make Purify to not find leaks where they don't
> exist. The purpose of debuggers is to report the truth about what is
> happening in a program. If the debugger lies, then it's not doing its
> job.
Purify is doing it's job just fine. It is correctly reporting a
memory leak. It just happens to be a memory leak that occurs only
after main() finishes--a memory leak that no one would ever care
about, and only occurs because of some C++ arcana that C++ gives you
no control over.
|>oug /\lan
<nessus@mit.edu>
Author: nessus@mit.edu (Douglas Alan)
Date: Mon, 12 Sep 1994 17:59:30 GMT Raw View
In article <TMB.94Sep12130931@arolla.idiap.ch> tmb@arolla.idiap.ch
(Thomas M. Breuel) writes:
> |Maybe the company should make Purify to not find leaks where they don't
> |exist. The purpose of debuggers is to report the truth about what is
> |happening in a program. If the debugger lies, then it's not doing
> |its [job]
> For the record, this is not a mistake Purify makes. It is a problem
> with the way Douglas uses destructors. From his description, Purify
> correctly reports that his program has a memory leak, since, at exit,
> he has unreferenced memory. The same leaks would be reported by
> Boehm's collector, for example.
For the record, this not a problem with the way I use destructors. It
is a problem with C++ that C++ won't allow me to specify how I would
like the destructors to be used. If I could tell C++ that I don't
want certain destructors to be called after the end of main(), then I
wouldn't have a problem.
|>oug /\lan
<nessus@mit.edu>
Author: tmb@arolla.idiap.ch (Thomas M. Breuel)
Date: 13 Sep 1994 00:41:41 GMT Raw View
|> For the record, this is not a mistake Purify makes. It is a problem
|> with the way Douglas uses destructors. From his description, Purify
|> correctly reports that his program has a memory leak, since, at exit,
|> he has unreferenced memory. The same leaks would be reported by
|> Boehm's collector, for example.
|
|For the record, this not a problem with the way I use destructors. It
|is a problem with C++ that C++ won't allow me to specify how I would
|like the destructors to be used. If I could tell C++ that I don't
|want certain destructors to be called after the end of main(), then I
|wouldn't have a problem.
You leave unreferenced objects in the heap, and Purify reports them.
That's exactly what Purify was designed to do.
If you don't want destructors called, you yourself have already come
up with at least one way of doing it (A *a = new A;). You could also
have a global boolean variable "no_more_destructors" that you set in
"main" and check in your destructors. So, there is nothing in C++
that keeps you from doing this either. If you want a quick-and-dirty
solution, you can also send your self a SIGKILL at the end of main or
call _exit; for debugging at least, that should be perfectly adequate.
Thomas.
Author: bill@amber.ssd.csd.harris.com (Bill Leonard)
Date: 13 Sep 1994 20:37:21 GMT Raw View
In article <NESSUS.94Sep8195051@twitch.mit.edu>, nessus@mit.edu (Douglas Alan) writes:
> What happens now when our program terminates? After main() is done,
> 'objectCache's destructor is executed, which then (needlessly) deletes
> the 'Object**' array that holds all the Object pointers in the vector.
> But 'Vector<Object*>::~Vector()' does not delete the myriad Object
> pointers that were in the vector. After the destructor finishes
> running, Purify's garbage collector kicks in and finds all the Objects
> that used to be pointed to by 'objectCache'. Since the vector has
> been destroyed, none of these Objects are now referenced by anything,
> and Purify thinks that it has discovered many memory leaks.
This seems like a design flaw to me, somewhere. If the Vector destructor
doesn't delete all those Object objects, who does? What if this were NOT a
global Vector, but some local Vector object in a procedure? Wouldn't this
cause a real memory leak?
If there is some "external" mechanism that is cleaning up those Object
objects, how come it isn't invoked on the global Vector?
I'd say there's a bug in the Vector destructor, based on what I got from
Douglas' description, but that may be too little information on which to
base a conclusion. It does seem to me, however, that a design that
necessitates not destroying an object, ever, to work correctly has a flaw
somewhere.
I do agree that it seems a waste of time, usually, to run destructors on
global objects, but not because of the reason Douglas advances. It is just
inefficient.
--
Bill Leonard
Harris Computer Systems Division
2101 W. Cypress Creek Road
Fort Lauderdale, FL 33309
Bill.Leonard@mail.csd.harris.com
These opinions and statements are my own and do not necessarily reflect the
opinions or positions of Harris Corporation.
------------------------------------------------------------------------------
More people run Windows on their home computers than on any other home
appliance.
------------------------------------------------------------------------------
Author: michaelc@lna.logica.com
Date: Tue, 13 Sep 1994 17:09:22 GMT Raw View
>>>>> "nessus" == Douglas Alan <nessus@mit.edu> writes:
nessus> THEREFORE, I propose [...]
Okay, but you're required to submit one of your livers with each proposal you
make. That way, people will only make proposals for extensions they
absolutely can't live without, and each person will be limited to two
proposals.
Anyhow, your proposal sounds like a quality of implementation issue. Most of
the time, a compiler could determine that it can optimize away the call of the
destructor for static objects. Or a compiler could offer explicit control via
a command-line switch, #pragmas or keywords (e.g., __no_destroy).
M.
Author: kohtala@hardy.trs.ntc.nokia.com (Kohtala Marko)
Date: 13 Sep 1994 20:41:48 GMT Raw View
In article <NESSUS.94Sep12135055@twitch.mit.edu> nessus@mit.edu (Douglas Alan) writes:
> Exactly. Which is why C++ should have a better mechanism than it does
> for allowing a programmer to specify on an object-by-object basis
> which global objects should have their destructors invoked after the
> end of main().
Why would you not want the destructors to be called? Can the objects
not have a state where the destructor does not do the thing which you
do not want them to do?
> No, I'm saying that the programming language should give the
> programmer control of what is happening.
Now this is something I agree with. C++ has a deficiency in global
variables' constructor and destructor calling order. But rather than
worrying about leaving destructors not called, I'd worry about the
order of construction and destruction. Some compilers have their own
hacks to get around this problem, others have headers contain static
variables whose constructors guarantee that things declared in the
header are initialized before they are used. Those variables are just
waste that could be removed with a way to tell which variables use
what other variables and have the construction order be generated from
that.
Here is something for a beginning of a solution ([] denoting optional,
"" keyword)
init-declarator:
declarator [initializer]
declarator [decl-dependency-list]
initializer:
... just like usual
initializer-clause:
assignment-expression [decl-dependency-list]
...
decl-dependency-list:
"using" expression
"using" "{" expression-list [","] "}"
expression-list:
expression
expression-list "," expression
For example:
Type globalInstance = initialValue using otherGlobalInstance;
Type globalInstance2 using { otherGlobalInstance, stillOther }, gInstance;
Type ta[] = { Type(&ta[1]) using ta[1], Type(0) }; // Too tricky?
Class declarations surely could have decl-dependency-list, since
requiring that the decl-dependency-list be copied to each instance of
the class is a far too much to ask from the programmer. I guess it
should be allowed to be placed into the constructor and destructor
declarations.
I am not sure if this should be allowed for function declarations
too. What would you precisely do with the knowledge that a function
uses some global variable? It would be great for documentation, should
be propagated to callers. But what then? Ideally they would propagate
to the constructors and destructors of some class and to the classes
declaration dependency list, but that is not possible and more likely
just prohibits useful things from being done.
Static local variables should be constructed when control passes first
time through it's declaration, destruction in reverse order.
Some platforms might have implementation problems, but mostly it
should be no more serious than the proper construction and destruction
already produce. Linker could then report the error of two global
variables depending on each other during construction or destruction.
Ok, I could be easily spoken out of this, but I hope someone get's a
usefull idea from this.
--
---
Marko Kohtala - Marko.Kohtala@ntc.nokia.com, Marko.Kohtala@hut.fi
Author: nessus@mit.edu (Douglas Alan)
Date: Tue, 13 Sep 1994 21:57:58 GMT Raw View
In article <TMB.94Sep13024141@arolla.idiap.ch> tmb@arolla.idiap.ch
(Thomas M. Breuel) writes:
> You leave unreferenced objects in the heap, and Purify reports them.
> That's exactly what Purify was designed to do.
I never said that I have any complaints with Purify.
> If you don't want destructors called, you yourself have already come
> up with at least one way of doing it (A *a = new A;).
Yes, this is what I am doing (actually "A& a = *new A;"), but I think
it is gross.
> You could also have a global boolean variable "no_more_destructors"
> that you set in "main" and check in your destructors.
I didn't write class RWTPtrOrderedVector, Rogue Wave Software
Inc. did. Are you saying that I should write my own class that
duplicates all the functionality of RWTPtrOrderedVector except that it
has a slightly different destructor, just so that I can get some
behavior that C++ should support to begin with? This does great
things for the notion of reusable software....
> So, there is nothing in C++ that keeps you from doing this either.
There's nothing that keeps me from implementing my program on a Turing
Machine. The question is how elegant, maintainable, and efficient my
program will end up being.
> If you want a quick-and-dirty solution, you can also send your self
> a SIGKILL at the end of main or call _exit; for debugging at least,
> that should be perfectly adequate.
No, I want the correct solution -- which C++ doesn't provide me with
the ability to implement. The solution I am using is the best
solution that C++ provides, but it suffers in elegance,
maintainability, and efficiency.
|>oug /\lan
<nessus@mit.edu>
Author: nessus@mit.edu (Douglas Alan)
Date: Tue, 13 Sep 1994 23:01:59 GMT Raw View
In article <KOHTALA.94Sep13234149@hardy.trs.ntc.nokia.com>
kohtala@hardy.trs.ntc.nokia.com (Kohtala Marko) writes:
> Why would you not want the destructors to be called?
Perhaps you missed my original posting in this thread that explained
why 99% of the time I don't want the destructor of a global object to
be called. I'll briefly recap:
(1) In 99 out of 100 cases a destructor just returns memory to the
heap. This is at best a waste of time if the program is just
going to terminate after all this is done anyway. In some
programs with large global objects, this may actually
take a long time.
(2) It causes my debugger to report that I have a memory leak
because C++ calls the destructor on my global Vector<Object*>.
Since the Vector has been destroyed, but the 'Object*'s were
not, the debugger detects a memory leak.
> Can the objects not have a state where the destructor does not do
> the thing which you do not want them to do?
Who says I implemented the object? Or that I have the source code for
it? Or that I want my general purpose container object to be
cluttered up with such tripe?
|>oug /\lan
<nessus@mit.edu>
Author: vinoski@apollo.hp.com (Steve Vinoski)
Date: Tue, 13 Sep 1994 23:08:11 GMT Raw View
In article <MCOOK.94Sep13130922@galaga.lna.logica.com> michaelc@lna.logica.com writes:
>>>>>> "nessus" == Douglas Alan <nessus@mit.edu> writes:
>
> nessus> THEREFORE, I propose [...]
>
>Okay, but you're required to submit one of your livers with each proposal you
>make. That way, people will only make proposals for extensions they
>absolutely can't live without, and each person will be limited to two
>proposals.
Being a homebrewer, I sometimes wish I had two livers. :-)
If you're going to copy Jim Waldo's statement from his 1992 Usenix C++
Conference presentation, the least you could do attribute the source
of the quote, and get it right. Jim said that everyone suggesting an
extension to C++ should have to send a kidney in with their
suggestion, since that way we'd know they were serious about the
proposal, and any one person could only suggest a maximum of two
extensions. :-)
--steve
Steve Vinoski vinoski@ch.hp.com (508)436-5904
Distributed Object Computing Program fax: (508)436-5122
Hewlett-Packard, Chelmsford, MA 01824
Author: nessus@mit.edu (Douglas Alan)
Date: Thu, 8 Sep 1994 23:50:51 GMT Raw View
In C++ the constructor for a global object is executed before main()
and the destructor for a global object is executed after main(). (By
"global object", I mean an object that is statically allocated, and is
accessed via a global variable or a static data member of a class.)
Executing such destructors after main() is elegantly symmetric, but 99
out of 100 times is NOT what I want; the program is just going to
terminate anyway after it finishes running these destructors, so why
waste any time performing memory managment, where none is needed?
Sure, sometimes a destructor closes a file or does something else that
needs to be done before the program ends, but as I said before,
perhaps this is the case one out of a hundred times.
You may say to yourself, "This isn't a problem. How long could it
take to run those useless destructors -- a few miliseconds?" Sure,
maybe in some programs it is not a problem, but in other programs
these destructor calls may take a long time. All of that time is
utterly wasted.
In my programs so far this hasn't been the problem, however. So, why
am I whining? The problem I have encountered is in conjunction with
the debugging tool Purify.
Purify detects memory leaks by running a garbage collector immediately
before the program terminates. If Purify finds any unreferenced
objects, then these are reported as leaks. This is superior to most
memory leak detectors, which just report all memory that had not been
returned to the heap when the program exited. This latter method is
inferior because it will incorrectly report as leaks objects that are
referenced by global objects.
How does Purify interact with C++? Let's think about a test case.
Let's say our program has a global variable called 'objectCache'.
Furthermore suppose that 'objectCache' is of type 'Vector<Object*>'.
What happens now when our program terminates? After main() is done,
'objectCache's destructor is executed, which then (needlessly) deletes
the 'Object**' array that holds all the Object pointers in the vector.
But 'Vector<Object*>::~Vector()' does not delete the myriad Object
pointers that were in the vector. After the destructor finishes
running, Purify's garbage collector kicks in and finds all the Objects
that used to be pointed to by 'objectCache'. Since the vector has
been destroyed, none of these Objects are now referenced by anything,
and Purify thinks that it has discovered many memory leaks.
In fact, Purify has not found a true memory leak, and Purify would
have worked correctly, and the program would have run faster if that
pesky destructor hadn't been called.
THEREFORE, I propose that there should be a mechanism for declaring a
global object for which the object's destructor will not be invoked at
the program's end.
Of course, there are existing workarounds for the problem I have
outlined. The best one is probably to define 'objectCache' as a
reference like this:
Vector<Object*>& objectCache = *new Vector<Object*>;
rather than as you normally would like this:
Vector<Object*> objectCache;
This solution is rather ugly, though, for something you want to do 99
out of 100 times. Also, this workaround entails that every reference
to objectCache will have to go through an unnecessary pointer
dereference. There should be a better way.
|>oug /\lan
<nessus@mit.edu>