473,769 Members | 1,929 Online
Bytes | Software Development & Data Engineering Community
+ Post

Home Posts Topics Members FAQ

Destructors are useless?

I've been having problem with destructors in the context of having ported C# code
developed under .NET to Mono. What happens is that, on a dual-CPU machine, various
parts of the code crash randomly (and rarely). This always happens during
process shutdown, after some thread has called System.Environm ent.Exit(). Clearly,
some sort of race condition.

Note that what follows only applies to destructors that are called when the process
shuts down -- there is no problem with destructors that get called by the GC when
the process is executing normally.

After a lot of debugging, I tracked it down to a destructor along the following
lines:

~SomeClass
{
if (!_destroyed)
{
System.Console. Error.WriteLine ("Forgot to destroy SomeClass");
}
}

Under Mono, the attempt to write to the console crashes the process because, by the time
destructor is called by the garbage collector, the I/O subsystem has been partially
garbage collected already, and the process dies with a NullPointerExce ption somewhere
in the guts of the I/O subsystem.

Not a problem with .NET: in .NET, the console isn't destroyed until after everything
else is destroyed. (For details, see:
http://www.bluebytesoftware.com/blog...3-20c06ae539ae
There is a comment about two-thirds of the way down the page by Brian Grunkemeyer to that effect.)

Except that I can't rely on this because, as far as I can see, the spec doesn't guarantee that
the console will hang around during process shutdown, so it's not portable code.

Then I started reading the spec a bit more and found that destruction order is not guaranteed and
that, if A refers to B, it's entirely possible for B to be finalized before A. So, that got me to
thinking about what is actually legal to do from within a destructor, if that destructor may be
called during process shutdown. Here is a list of things that are *not* legal to do:

- I cannot dereference anything. If I do, the memory for the object that is reference is guaranteed
to still be there. However, that object may have been finalized already and, as a result, may no
long be in working order if I call a method on it.

- I cannot call a static method on anything. The static method itself is guaranteed to be there. However, the
implementation of the static method may depend on another static object that has been finalized already.
See previous point.

- I cannot safely invoke a virtual method on my own object. That's because my own object may have a derived
part, and that derived part may have been finalized already, and the virtual method may end up using
something in the derived part that conceptually no longer exists.

So, that doesn't leave a lot I can do in a destructor, as far as I can see. Here is what I can do safely:

I can assign or read any of my own data members, and any of the accessible data members of my base class.
That's about it.

So, I can assign null to all my data members that have reference type, just to be nice to the GC. But that
really isn't all that essential in most circumstances.

I can read my own data members. To what purpose? Well, to assert that my program state is still in fine
shape, of course:

~SomeClass()
{
System.Diagnost ics.Assert(_myM ember != null);
System.Diagnost ics.Assert(_myO therMember == null);
}

Oops. I can't safely call a static method, because whatever the implementation of the static method uses may
have been finalized already. Just as I can't safely write to the console, I can't safely assert either.

Of course, I have no real control over when destructors are called. In particular, I have not control
over what destructors run when some thread calls Exit(). As a result, these restrictions apply to *all*
destructors, not just those of static objects.

Hmmm... That leaves destructors completely useless. There is nothing, not even asserting, that I can do safely.
Of course, that begs the question: why have destructors when I can't do anything with them? As far as I can
see, the only legal statements inside a destructor are effectively no-ops.
Mystified,

Michi.
Nov 17 '05 #1
26 2721
It is recommended to avoid using finalizers (they are actually called
'finalizers', not 'desctructors') wherever possible. If you need to release
unmanaged resources, implement IDisposable and follow the IDisposable
implementation design pattern recommended by Microsoft. If your class does
not use anything to be disposed, do not use the finalizer at all.

Still, you can put the finalizer to a good use implementing the Microsoft's
disposable object design pattern. In the pattern, the finalizer is the last
chance to release managed resources owned by the instance.

--
Sincerely,
Dmytro Lapshyn [Visual Developer - Visual C# MVP]
"Michi Henning" <mi***@zeroc.co m> wrote in message
news:On******** ******@tk2msftn gp13.phx.gbl...
I've been having problem with destructors in the context of having ported
C# code
developed under .NET to Mono. What happens is that, on a dual-CPU machine,
various
parts of the code crash randomly (and rarely). This always happens during
process shutdown, after some thread has called System.Environm ent.Exit().
Clearly,
some sort of race condition.

Note that what follows only applies to destructors that are called when
the process
shuts down -- there is no problem with destructors that get called by the
GC when
the process is executing normally.

After a lot of debugging, I tracked it down to a destructor along the
following
lines:

~SomeClass
{
if (!_destroyed)
{
System.Console. Error.WriteLine ("Forgot to destroy SomeClass");
}
}

Under Mono, the attempt to write to the console crashes the process
because, by the time
destructor is called by the garbage collector, the I/O subsystem has been
partially
garbage collected already, and the process dies with a
NullPointerExce ption somewhere
in the guts of the I/O subsystem.

Not a problem with .NET: in .NET, the console isn't destroyed until after
everything
else is destroyed. (For details, see:
http://www.bluebytesoftware.com/blog...3-20c06ae539ae
There is a comment about two-thirds of the way down the page by Brian
Grunkemeyer to that effect.)

Except that I can't rely on this because, as far as I can see, the spec
doesn't guarantee that
the console will hang around during process shutdown, so it's not portable
code.

Then I started reading the spec a bit more and found that destruction
order is not guaranteed and
that, if A refers to B, it's entirely possible for B to be finalized
before A. So, that got me to
thinking about what is actually legal to do from within a destructor, if
that destructor may be
called during process shutdown. Here is a list of things that are *not*
legal to do:

- I cannot dereference anything. If I do, the memory for the object that
is reference is guaranteed
to still be there. However, that object may have been finalized already
and, as a result, may no
long be in working order if I call a method on it.

- I cannot call a static method on anything. The static method itself is
guaranteed to be there. However, the
implementation of the static method may depend on another static object
that has been finalized already.
See previous point.

- I cannot safely invoke a virtual method on my own object. That's because
my own object may have a derived
part, and that derived part may have been finalized already, and the
virtual method may end up using
something in the derived part that conceptually no longer exists.

So, that doesn't leave a lot I can do in a destructor, as far as I can
see. Here is what I can do safely:

I can assign or read any of my own data members, and any of the accessible
data members of my base class.
That's about it.

So, I can assign null to all my data members that have reference type,
just to be nice to the GC. But that
really isn't all that essential in most circumstances.

I can read my own data members. To what purpose? Well, to assert that my
program state is still in fine
shape, of course:

~SomeClass()
{
System.Diagnost ics.Assert(_myM ember != null);
System.Diagnost ics.Assert(_myO therMember == null);
}

Oops. I can't safely call a static method, because whatever the
implementation of the static method uses may
have been finalized already. Just as I can't safely write to the console,
I can't safely assert either.

Of course, I have no real control over when destructors are called. In
particular, I have not control
over what destructors run when some thread calls Exit(). As a result,
these restrictions apply to *all*
destructors, not just those of static objects.

Hmmm... That leaves destructors completely useless. There is nothing, not
even asserting, that I can do safely.
Of course, that begs the question: why have destructors when I can't do
anything with them? As far as I can
see, the only legal statements inside a destructor are effectively no-ops.
Mystified,

Michi.


Nov 17 '05 #2
On Wed, 27 Apr 2005 17:40:26 +1000, Michi Henning wrote:
Hmmm... That leaves destructors completely useless. There is nothing, not even asserting, that I can do safely.
Of course, that begs the question: why have destructors when I can't do anything with them? As far as I can
see, the only legal statements inside a destructor are effectively no-ops.


If you are writing all managed code, then this statement is essentially it.
Within a pure managed environment this is no need to write a destructor.
Destructors are useful for releasing unmanaged resources. The garbage
collector has no specific knowledge on how to clean up an unmanaged
resource. Your class that encapsulates an unmanaged resource does have
that knowledge. So in your destructor you would take the responsibility
for making sure any unmanaged resources are properly released. Let the GC
handle your managed resources.

Also note that adding a destructor unnecessarily to an object can have a
negative impact on performance. An object with a destructor requires at
least two garbage collections. When the GC runs it reclaims the memory for
inaccessible objects without destructors. It cannot, at that time, collect
the inaccessible objects that do have destructors. Instead it places them
in a list of objects that are marked as ready for finalization. It then
calls the finalize method (destructor) on the objects in that list and then
removes them from the list. The next time GC runs it can them collect
these objects as they are now no longer in the list of objects that are
ready for finalization. So the fact that you have added a destructor to
your object only increases the chances that it will hang around longer than
other objects.
--
Tom Porterfield
Nov 17 '05 #3
Tom Porterfield wrote:
On Wed, 27 Apr 2005 17:40:26 +1000, Michi Henning wrote: If you are writing all managed code, then this statement is essentially it.
Well,... not quite, but perhaps that why you write "essentiall y"?
Within a pure managed environment this is no need to write a destructor.


Destructors can be quite usefull for checking that appropriate action
has been done on objects. For example you can check whether Dispose()
has been called.

The really usefull information: who forgot to call Dispose, is of course
not available, but I have a nice MixIn class that I can use to record
the stacktrace when the object is created and that is definatly also
usefull to know. It gives you a place to start looking.

While you may not be able to do much with the knowledge in your
finalizer, you can certainly throw an exception (the program *IS*
broken,... it forgot to call Dispose()) and the runtime may do
"good-things" with it. In debug mode the Visual IDE pops up a
messagebox, alerting you to a bug which would otherwise go unnoticed.

You can have your Dispose() call "GC.SuppressFin alize(this)" and avoid
invocation of the destructor.

--
Helge Jensen
mailto:he****** ****@slog.dk
sip:he********* *@slog.dk
-=> Sebastian cover-music: http://ungdomshus.nu <=-
Nov 17 '05 #4
"Tom Porterfield" <tp******@mvps. org> wrote in message
news:7t******** ******@tpporter mvps.org...
On Wed, 27 Apr 2005 17:40:26 +1000, Michi Henning wrote:
Hmmm... That leaves destructors completely useless. There is nothing, not even asserting, that I can do safely. Of course, that begs the question: why have destructors when I can't do anything with them? As far as I can see, the only legal statements inside a destructor are effectively no-ops.
If you are writing all managed code, then this statement is essentially it.


Yes, it sure looks like it.
Within a pure managed environment this is no need to write a destructor.
Destructors are useful for releasing unmanaged resources. The garbage
collector has no specific knowledge on how to clean up an unmanaged
resource. Your class that encapsulates an unmanaged resource does have
that knowledge. So in your destructor you would take the responsibility
for making sure any unmanaged resources are properly released. Let the GC
handle your managed resources.


Sure, I understand all that. A destructor can't do anything that relies on
another
object. In other words, a destructor can't dereference anything, and it cannot
even call a static function. Fine, I accept that. So what are destructors good
for?
Releasing unmanaged resources is one thing. (If I can accept that unmanaged
resources may not be released promptly.) But the *one thing* I still would
like to use destructors for is to check whether my program is still in a sane
state. In other words, I'd like to write assertions about my own member
variables. Unfortunately, I can't even do that: if the assertion holds, no
problem;
but, if the assertion fails, the mere act of having it fail is sufficient to
cause
undefined behavior. So, damned if I do, and damned if don't: if I don't assert,
my program has a bug and will go and do undefined things. If I do assert, the
act
of detecting that my program has a bug causes my program to go and do
undefined things. Talk about being caught between a rock and a hard place...

It is interesting that Java doesn't have this problem. On process exit, Java
guarantees
that destructors (or finalizers, if you prefer) will *not* run. This means that
it's perfectly
safe to put assertions into Java finalizers: while the process is alive and
running, the
assertions are checked whenever the GC decides to garbage collect an object.
And, if the process is exiting, destructors don't run at all, meaning that
there are
no issues with respect to destruction order. Of course, that means that
assertions
don't run during process shut down, but at least my process gets to shut down
without crashing.

And, of course, the idea that C# destructors are good for releasing unmanaged
resources seems flawed, too. After all, how many realistic programs are there
that can release unmanged resources without having to enrol the aid of some
other
managed resource? For example, in my particular case, the program crashed
because
of a destructor that looked like this:

class SomeClass
{
~SomeClass()
{
if(_destroyed)
{
_logger.warning ("Forgot to destroy SomeClass");
}
}

private Logger _logger;
}

What I'm suggesting here is that, for realistic programs, chances are pretty
slim
that a destructor can clean up an unmanaged resource without having to ask for
help from some managed object. But, of course, as soon as that is the case,
the destructor cannot be used anymore. I'm left to conclude that destructors
are so useless that I can't even use them to make assertions about my own
member variables, let alone use them to reclaim unmanaged resources.

It truly would seem appropriate to remove destructors from the language
altogether. At least, that way, people wouldn't have their illusions shattered.

Of course, the alternative would be to not run destructors on process shutdown,
like Java does, and the problem would go away...

Cheers,

Michi.

--
Michi Henning Ph: +61 4 1118-2700
ZeroC, Inc. http://www.zeroc.com

Nov 17 '05 #5
"Helge Jensen" <he**********@s log.dk> wrote in message
news:uB******** ******@tk2msftn gp13.phx.gbl...
The really usefull information: who forgot to call Dispose, is of course
not available, but I have a nice MixIn class that I can use to record
the stacktrace when the object is created and that is definatly also
usefull to know. It gives you a place to start looking.

While you may not be able to do much with the knowledge in your
finalizer, you can certainly throw an exception (the program *IS*
broken,... it forgot to call Dispose()) and the runtime may do
"good-things" with it. In debug mode the Visual IDE pops up a
messagebox, alerting you to a bug which would otherwise go unnoticed.


Hmmm... The specification says that, if a destructor throws, the exception
is ignored and execution of that destructor is aborted. Given that you cannot
safely assert in a destructor or write to the console, it seems unlikely that
you could safely record a stack trace: after all, the classes you have to use
to record the stack trace may have long since been finalized. And, if you
can record the stack trace, throwing the exception is useless because that
exception won't go anywhere (that's guaranteed by the spec). Ergo,
destructors are useless during process shutdown. And, because I cannot
control when constructors run, they are useless altogether.

Cheers,

Michi.

--
Michi Henning Ph: +61 4 1118-2700
ZeroC, Inc. http://www.zeroc.com

Nov 17 '05 #6
> What I'm suggesting here is that, for realistic programs, chances are pretty
slim
that a destructor can clean up an unmanaged resource without having to ask for
help from some managed object. But, of course, as soon as that is the case,
the destructor cannot be used anymore. I'm left to conclude that destructors
are so useless that I can't even use them to make assertions about my own
member variables, let alone use them to reclaim unmanaged resources.
cannot make assertions hardly means can't reclaim unmanaged resources. the
framework itself already showed that realistic programs can free unmanaged
resources with finalizers. your illusion is the problem here, really. doesn't
do what you want it to do only means it's useless to you.

It truly would seem appropriate to remove destructors from the language
altogether. At least, that way, people wouldn't have their illusions shattered.
Of course, the alternative would be to not run destructors on process shutdown,
like Java does, and the problem would go away...

Cheers,

Michi.

--
Michi Henning Ph: +61 4 1118-2700
ZeroC, Inc. http://www.zeroc.com

Nov 17 '05 #7

Michi Henning wrote:
It is interesting that Java doesn't have this problem. On process exit, Java guarantees
that destructors (or finalizers, if you prefer) will *not* run. This means that it's perfectly
safe to put assertions into Java finalizers: while the process is alive and running, the
assertions are checked whenever the GC decides to garbage collect an object. And, if the process is exiting, destructors don't run at all, meaning that there are
no issues with respect to destruction order. Of course, that means that assertions
don't run during process shut down, but at least my process gets to shut down without crashing.


Ok -- so how about this:

class someclass
{
~someclass()
{
try
{
System.Diagnost ics.Assert(_myM emVar == null);
}
catch(ObjectDis posedException)
{
// you can't do much here anyway
}
}
}

Nov 17 '05 #8
Michi Henning wrote:
"Helge Jensen" <he**********@s log.dk> wrote in message
Hmmm... The specification says that, if a destructor throws, the exception
is ignored and execution of that destructor is aborted. Given that you cannot
So conforming runtimes should ignore the exception, that way semantics
is preserved.
safely assert in a destructor or write to the console, it seems unlikely that
you could safely record a stack trace: after all, the classes you have to use
It works well in practice, especially in debug mode.
to record the stack trace may have long since been finalized. And, if you
But i still have a trace indicating the stack that allocated the object
that wasn't Dispose'ed, that's a whole lot better than knowing nothing,
or just it's type.
can record the stack trace, throwing the exception is useless because that
exception won't go anywhere (that's guaranteed by the spec). Ergo,
It goes straight into a message-dialog in debug mode. I put the
stack-trace in the exception-message and that gives a really good place
to start bug-hunting.
destructors are useless during process shutdown. And, because I cannot
control when constructors run, they are useless altogether.


Try rereading my posting, i didn't say they were usefull for semantics,
but for finding bugs. For example the places where Dispose() has been
forgotten.

--
Helge Jensen
mailto:he****** ****@slog.dk
sip:he********* *@slog.dk
-=> Sebastian cover-music: http://ungdomshus.nu <=-
Nov 17 '05 #9
"Dilip" <rd*****@lycos. com> wrote in message
news:11******** **************@ l41g2000cwc.goo glegroups.com.. .
Ok -- so how about this:

class someclass
{
~someclass()
{
try
{
System.Diagnost ics.Assert(_myM emVar == null);
}
catch(ObjectDis posedException)
{
// you can't do much here anyway
}
}
}


No, don't think so. Under Mono, the code crashes with a NullPointerExce ption
if the assertion fails somewhere inside the assert. A far as I can see, the
problem
is that, by the time my finalizer runs, stdout and stderr have been closed
already
and further calls to assert crash the process.

Cheers,

Michi.
--
Michi Henning Ph: +61 4 1118-2700
ZeroC, Inc. http://www.zeroc.com

Nov 17 '05 #10

This thread has been closed and replies have been disabled. Please start a new discussion.

Similar topics

16
5292
by: David Turner | last post by:
Hi all I noticed something interesting while testing some RAII concepts ported from C++ in Python. I haven't managed to find any information about it on the web, hence this post. The problem is that when an exception is raised, the destruction of locals appears to be deferred to program exit. Am I missing something? Is this behaviour by design? If so, is there any reason for it? The only rationale I can think of is to speed up...
9
1642
by: David Turner | last post by:
Further to the discussion from earlier titled "does python have useless destructors?", I've posted a brief summary of garbage collection, RAII, and the "using" clause (which is what PEP310's "with" clause amounts to). The post is here: http://dkturner.blogspot.com/2004/06/garbage-collection-raii-and-using.html Regards David Turner
6
2185
by: Elbert Lev | last post by:
Please correct me if I'm wrong. Python (as I understand) uses reference counting to determine when to delete the object. As soon as the object goes out of the scope it is deleted. Python does not use garbage collection (as Java does). So if the script runs a loop: for i in range(100): f = Obj(i)
3
21391
by: Rajesh Garg | last post by:
Can we have private constructors and destructors? IF yes what is the use of such constructors or destructors.....in the sense where can these be implemented in a system................. I have an idea that we can have private constructors and destructors but am not able to find a situation where they can be used... Regards RVG rajeshgarg@opussoft.com
8
1807
by: Edward Diener | last post by:
I have a __value class which uses some legacy C++ code. So I wrapped the legacy C++ code in another __nogc class and have a pointer to that class as a member of my __value class. When the __value class is created, I dynamically allocate an object of the class with the legacy C++ code. However because the __value class has no destructor, I can never release that allocated memory. Why does a __value class allow no destructor ? Without it I...
3
1894
by: alex.gman | last post by:
If I have code like this int f() { // ... stuff ... g(); if(x > 0) return (x+4); // ... more stuff ... always_call(z); return y; }
6
7530
by: mlw | last post by:
Could someone explain why there is no destructor in Java classes? There are many times you need to be called WHEN an object goes out of scope and not when it will eventally be freed.
5
2084
by: Rennie deGraaf | last post by:
I know that if an exception is thrown from a destructor while unwinding the stack because of another exception, terminate() is called (FAQ 17.3). How exactly does this rule work? Is it acceptable to both throw /and/ catch an exception inside a destructor, as in the following code? struct Foo { void finalize() { throw 1.0;
6
5165
by: Jeff Newman | last post by:
Hello, Could anyone explain to me why the following class's destructor shows up as having multiple branches? (At least as judged by gcov 4.1.2 when compiled with gcc 4.1.2 ): struct blah { blah(); virtual ~blah();
0
9579
marktang
by: marktang | last post by:
ONU (Optical Network Unit) is one of the key components for providing high-speed Internet services. Its primary function is to act as an endpoint device located at the user's premises. However, people are often confused as to whether an ONU can Work As a Router. In this blog post, we’ll explore What is ONU, What Is Router, ONU & Router’s main usage, and What is the difference between ONU and Router. Let’s take a closer look ! Part I. Meaning of...
0
9420
by: Hystou | last post by:
Most computers default to English, but sometimes we require a different language, especially when relocating. Forgot to request a specific language before your computer shipped? No problem! You can effortlessly switch the default language on Windows 10 without reinstalling. I'll walk you through it. First, let's disable language synchronization. With a Microsoft account, language settings sync across devices. To prevent any complications,...
0
10205
Oralloy
by: Oralloy | last post by:
Hello folks, I am unable to find appropriate documentation on the type promotion of bit-fields when using the generalised comparison operator "<=>". The problem is that using the GNU compilers, it seems that the internal comparison operator "<=>" tries to promote arguments from unsigned to signed. This is as boiled down as I can make it. Here is my compilation command: g++-12 -std=c++20 -Wnarrowing bit_field.cpp Here is the code in...
1
9984
by: Hystou | last post by:
Overview: Windows 11 and 10 have less user interface control over operating system update behaviour than previous versions of Windows. In Windows 11 and 10, there is no way to turn off the Windows Update option using the Control Panel or Settings app; it automatically checks for updates and installs any it finds, whether you like it or not. For most users, this new feature is actually very convenient. If you want to control the update process,...
0
8863
agi2029
by: agi2029 | last post by:
Let's talk about the concept of autonomous AI software engineers and no-code agents. These AIs are designed to manage the entire lifecycle of a software development project—planning, coding, testing, and deployment—without human intervention. Imagine an AI that can take a project description, break it down, write the code, debug it, and then launch it, all on its own.... Now, this would greatly impact the work of software developers. The idea...
1
7401
isladogs
by: isladogs | last post by:
The next Access Europe User Group meeting will be on Wednesday 1 May 2024 starting at 18:00 UK time (6PM UTC+1) and finishing by 19:30 (7.30PM). In this session, we are pleased to welcome a new presenter, Adolph Dupré who will be discussing some powerful techniques for using class modules. He will explain when you may want to use classes instead of User Defined Types (UDT). For example, to manage the data in unbound forms. Adolph will...
0
6662
by: conductexam | last post by:
I have .net C# application in which I am extracting data from word file and save it in database particularly. To store word all data as it is I am converting the whole word file firstly in HTML and then checking html paragraph one by one. At the time of converting from word file to html my equations which are in the word document file was convert into image. Globals.ThisAddIn.Application.ActiveDocument.Select();...
0
5293
by: TSSRALBI | last post by:
Hello I'm a network technician in training and I need your help. I am currently learning how to create and manage the different types of VPNs and I have a question about LAN-to-LAN VPNs. The last exercise I practiced was to create a LAN-to-LAN VPN between two Pfsense firewalls, by using IPSEC protocols. I succeeded, with both firewalls in the same network. But I'm wondering if it's possible to do the same thing, with 2 Pfsense firewalls...
0
5441
by: adsilva | last post by:
A Windows Forms form does not have the event Unload, like VB6. What one acts like?

By using Bytes.com and it's services, you agree to our Privacy Policy and Terms of Use.

To disable or enable advertisements and analytics tracking please visit the manage ads & tracking page.