Destructor: not gauranteed to be called?

J

James Park

Peter Oliphant said:
Correct. Since ToString is not part of the C++ standard it doesn't make
much sense to justify broken behavior on its part as being required
because it IS standard behavior! Now, check out this link:

http://lab.msdn.microsoft.com/produ...edbackid=788dfe28-0ac8-4c5f-96c2-bb5065fd7c2b

Recapping, the ToString() function applied to a Char[] results in EXACTLY
the following string regardless of the contents of the Char[] :
"System.Char[]". MS claims that they will not 'fix' ToString to solve
this, but instead might create a new function to accomplished more natural
and desired results. They say that changing the current behavior would be
'breaking'. That could only be true if enough people out there wrote code
that RELIES on this behavior. Who would rely on this behavior is what I
was saying in the above? It sounds like just an excuse NOT to change it
since they don't think of it as important enough.

There are a lot of programmers out there, and I wouldn't doubt someone
somewhere is relying on it somehow. To now implement Array.ToString() at the
risk of breaking code solely for functionality that can just as easily be
obtained through the String constructor isn't worth it in my opinion.
 
A

Arnaud Debaene

Peter said:
- The fact yhat you require or expect a Char[] to act as a string is
a sign that you are still in a "C" way of though. In OOP, a string is
an object in itself, and an char array is just an array, it has
nothing to do with strings. The fact that in C, a string is a char[]
is a kludge that has no reason to be in OOP.

Maybe. I don't think I'm expecting Char[] to act like a string, but
believe the natural and expected result of applying ToString to it
would return a contatenation of the characters in order in the array
as one string

Ok, then go the end of your reasoning : If Char[].ToString should return the
concatentation of the chars, then Int[].ToString should return the
concatenation of the ints, Float[].ToString should return the concatenation
of the floats (totally meaningless), and SomeObject[].ToString() should
return the concatenation of SomeObjects (probably totally meaningless
too)....
But, to the point. Can you think of a good justification as to why, no
matter what the contents of a Char[] are, that applying ToString to it
returns precisely this string and only this string every time:
"System.Char[]"?
Yes : coherence with all other arrays, see above...

Arnaud
MVP - VC
 
P

Peter Oliphant

But, to the point. Can you think of a good justification as to why, no
matter what the contents of a Char[] are, that applying ToString to it
returns precisely this string and only this string every time:
"System.Char[]"?

Then you believe that ToString applied to an Int[] should return this string
and only this string every time:

"System.Int[]" (which maybe it does, I don't know)

Similarly, a Float[] should always return "System.Float[]" etc. Then why
doesn't applying ToString to just an Int return this string every time
"System.Int".? Seems a bit inconsistent to me. Why not just disallow
ToString from being applied to an array, instead of a constant response.
ToString has no valuable use for arrays anyway if that's all it does...

I guess we just have to agree to disgree on this. I personally don't feel a
function called ToString's purpose would be to return a string representing
the Type of any array and not be concerned at all with it's contents. I
feel it is incorrectly named when applied to arrays, and probably should
generate a compiler error when attempted (as has been mentioned ToString()
has nothing to do with any C++ standard, so MS can chose how to deal with
ToString() as they please).

ToString() does work as I would expect it on most entities: int's, floats's,
etc. This in a sense was part of the problem. It worked as I assumed it
would work in most cases, so it took a while to realize it wasn't doing what
I expected in the case of giving it a 'char'. As previously discussed,
ToString doesn't return a string with the single character passed to it, but
rather a string representing the decimal ASCII value of the char, for
example, '0'.ToString() = "48" since the ASCII value for the character '0'
is 48. MS claims this is proper. I don't feel the same way. I expected that
'0'.ToString() = "0". Silly me...

But I guess that's becuase I'm still thinking in 'C' terms. I still think of
a 'char' as a variable used to represent a symbolic member of the extended
alphabet ('a'-'z', 'A'-'Z', '0'-'9', etc.). Apparently this is old thinking.
The 'new hotness' is that a 'char' is just a byte with an ASCII value in it
Of course this means there is no natural way to convert a 'char' to a
String, or to insert one. Now that's progress... : )

[==P==]


Arnaud Debaene said:
Peter said:
- The fact yhat you require or expect a Char[] to act as a string is
a sign that you are still in a "C" way of though. In OOP, a string is
an object in itself, and an char array is just an array, it has
nothing to do with strings. The fact that in C, a string is a char[]
is a kludge that has no reason to be in OOP.

Maybe. I don't think I'm expecting Char[] to act like a string, but
believe the natural and expected result of applying ToString to it
would return a contatenation of the characters in order in the array
as one string

Ok, then go the end of your reasoning : If Char[].ToString should return
the concatentation of the chars, then Int[].ToString should return the
concatenation of the ints, Float[].ToString should return the
concatenation of the floats (totally meaningless), and
SomeObject[].ToString() should return the concatenation of SomeObjects
(probably totally meaningless too)....
But, to the point. Can you think of a good justification as to why, no
matter what the contents of a Char[] are, that applying ToString to it
returns precisely this string and only this string every time:
"System.Char[]"?
Yes : coherence with all other arrays, see above...

Arnaud
MVP - VC
 
P

Peter Oliphant

Why would you think that when C++ makes no similar guarantee for pure native
C++? The destructor for an object on the heap is called when and if you
call delete on a pointer to that object. The situation is no different for
C++/CLI with respect the to destructor (which is IDisposable::Dispose for
C++/CLI).

I would think this for the following reasons. Let me make a real work
analogy. I do the laundry. Hence, at the end of the process, I shut down by
folding all the clothes and putting them away and turn off the lights. But
then a manager is appointed to the laundry room. He tell me I no longer need
to turn off the lights at the end of my laundry session since he will shut
down for me. This is a good thing since other people might want to use the
laundromat, and it's wasteful to keep turning the lights on and off.

So, what your telling me is this. Why would I expect the manager to turn off
the lights when he closes the landromat? Because he told me HE would be
responsible for shutting down! Further, I told him that when I did it
manually I turned off the lights. It's in my 'rules for shutting down'.

So, the reason I would expect for the system to gaurantee calling the
destructor when it GC's them is that when it took the responsibility for
shutting down it is implying it will do it PROPERLY. Obviously the whole
purpose of a destructor is to 'shut down properly'.

Don't know if you have any kids, but consider this. If you usually vacuumed
and you gave this responsibility to your kid, would you or would you not
assume he would put the vacuum cleaner away when he is done, or would you
expect to have to do this yourself?

[==P==]

Carl Daniel said:
Peter said:
I'm programming in VS C++.NET 2005 using cli:/pure syntax. In my code
I have a class derived from Form that creates an instance of one of
my custom classes via gcnew and stores the pointer in a member.
However, I set a breakpoint at the destructor of this instance's
class and it was never called!!! I can see how it might not get
called at a deterministic time. But NEVER?

So, I guess I need to know the rules about destructors. I would have
thought any language derived from C++ would always guarantee the
destructor of an instance of a class be called at some time,
especially if created via [gc]new and stored as a pointer.

Why would you think that when C++ makes no similar guarantee for pure
native C++? The destructor for an object on the heap is called when and
if you call delete on a pointer to that object. The situation is no
different for C++/CLI with respect the to destructor (which is
IDisposable::Dispose for C++/CLI).
Yes, I think I can deterministically destruct it via 'delete' and
setting to nullptr. But the point still kinda freaks me that the
destructor is no longer gauranteed to EVER be called. I feel like I
should be worried since it is sometimes important to do other things
besided freeing up memory in a destructor. In my case I discovered it
becuase I'm communicating through a serial port which I change the
baud rate from the current speed, but then changed it back in the
destructor - only to find out the destructor was NEVER called! Hence,
the port died, and MY program wouldn't work on subsequent runs since
it assumed the port had been returned to the same baud (and hence
couldn't communicate with it anymore).
So, again, why is the destructor no longer gauranteed to be called,
and what are these new rules? Or am I being ignorant, and C++ never
made such assurances. Inquiring minds want to know! : )

They're not new rules - it's the nature of objects on the heap. For
managed object on the GC heap, the Finalizer MAY be called if you don't
delete the object, but the CLR doesn't guarantee that finalizers are ever
called either.

-cd
 
P

Peter Oliphant

There are a lot of programmers out there, and I wouldn't doubt someone
That may very well be true, that someone has counted on this as its
behavior. This bodes a VERY BAD trend then, if this is justification for
having questionable behavior remain.

It sounds like this. If a bug remains in existence long enough, it will be
assumed someone is counting on this bug to keep behaving as it is, so we are
no longer 'allowed' to 'fix' the bug. We instead will call it a feature.

Note that it's not like someone has come forward and so "Wait! PLEASE don't
change the behavior of ToString becuase we are using it as it has been
implemented". No. It is being ASSUMED someone iMIGHT be using this. And
based on this ASSUMPTION we have to use workarounds?

I don't know about you, but to me, that's just STUPID....

[==P==]

James Park said:
Peter Oliphant said:
Correct. Since ToString is not part of the C++ standard it doesn't make
much sense to justify broken behavior on its part as being required
because it IS standard behavior! Now, check out this link:

http://lab.msdn.microsoft.com/produ...edbackid=788dfe28-0ac8-4c5f-96c2-bb5065fd7c2b

Recapping, the ToString() function applied to a Char[] results in EXACTLY
the following string regardless of the contents of the Char[] :
"System.Char[]". MS claims that they will not 'fix' ToString to solve
this, but instead might create a new function to accomplished more
natural and desired results. They say that changing the current behavior
would be 'breaking'. That could only be true if enough people out there
wrote code that RELIES on this behavior. Who would rely on this behavior
is what I was saying in the above? It sounds like just an excuse NOT to
change it since they don't think of it as important enough.

There are a lot of programmers out there, and I wouldn't doubt someone
somewhere is relying on it somehow. To now implement Array.ToString() at
the risk of breaking code solely for functionality that can just as easily
be obtained through the String constructor isn't worth it in my opinion.
 
P

Peter Oliphant

Don't know if you have any kids, but consider this. If you usually
vacuumed and you gave this responsibility to your kid, would you or would
you not assume he would put the vacuum cleaner away when he is done, or
would you expect to have to do this yourself?

We now eavesdrop on the Hendersons:

Dad: Well, My Son (MS), today I make you a man! I'm giving you the great
responsibility of doing the vacuuming. This is a tradition handed down from
generation-to generation, and today I give it to you! Excited?

MS: Yeah, sure, whatever (goes back to watching tv).

Dad: Ok, let me explain this to you. Whenever I do the vacuuming and I'm
done, I always put the vacuum away. Well, son, I've given you an important
responsibility. Think you can handle it?

MS: Sure, whatever (goes back to watching tv).

Dad: Great! I'll be back in a few hours. See you then...

[2 hours go by, Dad gets home.]

Dad: Hi son! I see things are very clean! Good job vacuuming! But wait! How
come the vacuum cleaner is still sitting in the middle of the room?

MS: Well, you said when you did the vacuuming yourself you use to put away
the vacuum cleaner. Why would you assume if I did the vacuum cleaning that I
would put it away?

Dad: Because when I give you a responsibility I expect you to perform it
properly! It must be obvious to you that when I said I put the vacuum
cleaner away when I'm done that that is the proper thing to do. I even gave
you full written instructions on how to do ths. Why didn't you follow those
instructions?

MS: Hey Dad! Just becuase I told you I would do the vacuuming for you
doesn't mean I have to do everything! After all, when you did the vacuuming
it was YOU who put the vaccum cleaner away. Why wouldn't you STILL be
responsible for putting the vacuum cleaner away, even if I told you I would
do the vacuuming?

Dad: I give up! Go to your room...!

[==P==]


Peter Oliphant said:
Why would you think that when C++ makes no similar guarantee for pure
native
C++? The destructor for an object on the heap is called when and if you
call delete on a pointer to that object. The situation is no different
for
C++/CLI with respect the to destructor (which is IDisposable::Dispose for
C++/CLI).

I would think this for the following reasons. Let me make a real work
analogy. I do the laundry. Hence, at the end of the process, I shut down
by folding all the clothes and putting them away and turn off the lights.
But then a manager is appointed to the laundry room. He tell me I no
longer need to turn off the lights at the end of my laundry session since
he will shut down for me. This is a good thing since other people might
want to use the laundromat, and it's wasteful to keep turning the lights
on and off.

So, what your telling me is this. Why would I expect the manager to turn
off the lights when he closes the landromat? Because he told me HE would
be responsible for shutting down! Further, I told him that when I did it
manually I turned off the lights. It's in my 'rules for shutting down'.

So, the reason I would expect for the system to gaurantee calling the
destructor when it GC's them is that when it took the responsibility for
shutting down it is implying it will do it PROPERLY. Obviously the whole
purpose of a destructor is to 'shut down properly'.

Don't know if you have any kids, but consider this. If you usually
vacuumed and you gave this responsibility to your kid, would you or would
you not assume he would put the vacuum cleaner away when he is done, or
would you expect to have to do this yourself?

[==P==]

Carl Daniel said:
Peter said:
I'm programming in VS C++.NET 2005 using cli:/pure syntax. In my code
I have a class derived from Form that creates an instance of one of
my custom classes via gcnew and stores the pointer in a member.
However, I set a breakpoint at the destructor of this instance's
class and it was never called!!! I can see how it might not get
called at a deterministic time. But NEVER?

So, I guess I need to know the rules about destructors. I would have
thought any language derived from C++ would always guarantee the
destructor of an instance of a class be called at some time,
especially if created via [gc]new and stored as a pointer.

Why would you think that when C++ makes no similar guarantee for pure
native C++? The destructor for an object on the heap is called when and
if you call delete on a pointer to that object. The situation is no
different for C++/CLI with respect the to destructor (which is
IDisposable::Dispose for C++/CLI).
Yes, I think I can deterministically destruct it via 'delete' and
setting to nullptr. But the point still kinda freaks me that the
destructor is no longer gauranteed to EVER be called. I feel like I
should be worried since it is sometimes important to do other things
besided freeing up memory in a destructor. In my case I discovered it
becuase I'm communicating through a serial port which I change the
baud rate from the current speed, but then changed it back in the
destructor - only to find out the destructor was NEVER called! Hence,
the port died, and MY program wouldn't work on subsequent runs since
it assumed the port had been returned to the same baud (and hence
couldn't communicate with it anymore).
So, again, why is the destructor no longer gauranteed to be called,
and what are these new rules? Or am I being ignorant, and C++ never
made such assurances. Inquiring minds want to know! : )

They're not new rules - it's the nature of objects on the heap. For
managed object on the GC heap, the Finalizer MAY be called if you don't
delete the object, but the CLR doesn't guarantee that finalizers are ever
called either.

-cd
 
A

aa

Those who routinely wrap unmanaged C++ code in C++/CLI feel unsafe,
especially when the wrapped assemblies are going to be used in C# or VB.

Let me help, Tom;

"Those who routinely wrap C++ code in CLI feel unsafe, especially when the
wrapped assemblies are going to be used in C# or VB.

See how clear the sentence becomes? How clear the problem becomes?
 
T

Tamas Demjen

Peter said:
So, the reason I would expect for the system to gaurantee calling the
destructor when it GC's them is that when it took the responsibility for
shutting down it is implying it will do it PROPERLY. Obviously the whole
purpose of a destructor is to 'shut down properly'.

You have a misconception here. If there is garbage collection, the GC
does clean up. It just calls the finalizer, not the destructor. There's
a big difference between the two. The destructor is responsible for
shutting down all the owned managed resources (deleting all owned
pointers, if you will). The finalizer, however, shouldn't call delete on
the owned managed pointers, because those pointers are GCed too. The GC
deletes them and calls the finalizer for them, so you're not supposed to
do anything with them, otherwise they would be double deleted. So the
finalizer should only release resources (closing files, mutexes), and
deallocate unmanaged memory. It should never destruct owned managed members.

You either destroy deterministically with the destructor, or if you fail
to do so, the GC will call the finalizer, but the cleanup task you need
to do in the finalizer is potentially different than in the destructor.

So the garbage collector does clean up -- when it runs at all, that is.
It's not guaranteed to run, because it only runs if the system thinks
it's low on memory. The reason behind this is that the OS cleans up for
you when your application exits anyway. It closes all the files you left
open, deletes all the memory you kept undeleted.

If your resources are critical and require prompt cleanup, you simply
can't leave them up to the garbage collector. Then you need
deterministic destruction. You have to think of the GC as a tool that
solves a lot of problems, but doesn't work in all cases. In some cases
you must ensure that you execute a certain function on destruction, and
not later than that. That's why reference counted smart pointers (or
whatever other techniques) still have their place. That's why C++/CLI
supports the stack syntax, which guarantees that the destrutor is called
when the object goes out of scope.

Tom
 
P

Peter Oliphant

Hi Tom,

So was this response (from earlier on in this thread) inaccurate:

"For managed object on the GC heap, the Finalizer MAY be called if you don't
delete the
object, but the CLR doesn't guarantee that finalizers are ever called
either."

This implies you can't count on either the destructor OR the finalizer from
ever being called. What you said seems to imply one of these WILL be called.
Which is true?

Would the following be accurate? If I create a ref class, I should only
create a destructor for it if I plan to delete some instances of it manually
on occasion (if I always did it this there would be very little reason to
make it a ref class since GC would never come into play) or if I plan to
create some instances in stack semantic form (and some that are not, or else
again, there would be very little reason to make it a ref class since GC
would never come into play).

That is, if all class instances will be deleted via the GC and are never
created in stack semantic form, then the destructor is just wasted code
taking up space. On the other side, if I can guarantee the instances will
always be stack semantic in form or they will always be manually deleted,
then the finalizer is wasted code taking up space. If neither of these is
true (the majority of cases), then the destructor is for manual deletion and
the finalizer for GC deletion.

Wow! So in the effort to make things more simple and automitized we have
added a new layer of complication: the finalizer! We now have TWO ways an
instance can be destroyed, and depending on who you believe, there may or
may not be any gaurantee that either will ever be called unless you manually
do it. So much for automation!

So, if I create any class for which a destructor must be eventually called
for every instance, then creating such a class as a ref class is purely
cosmetic when it comes to functionality...

[==P==]
 
D

Dilip

Brandon said:
To me, the biggest limitation
imposed on destructors as a result of IDisposable is that all destructors
are public and virtual. I actually that's a good thing, and it's a mistake
that unmanaged C++ allows destructors to be anything else.

My question below is related to pure Standard C++ and its a little bit
basic.

If as you say dtors were always public/virtual, how would you think of
re-implementing idioms, like classes that allow objects to be created
only on the heap, that depend on dtor being private/protected and
instead provide a public destroy() that simply performs a 'delete this'?
 
T

Tamas Demjen

Peter said:
This implies you can't count on either the destructor OR the finalizer from
ever being called. What you said seems to imply one of these WILL be called.
Which is true?

If GC is performed, it guarantees to call the finalizer. However,
garbage collection is not guaranteed to be done. Garbage is collected
when the system thinks it is necessary (for example, it runs out of
memory). However, when the application exits, it is not guaranteed to be
called, because the OS cleans up after each application anyway. You can
try to call GC::Collect manually.

The destructor is always guaranteed to be called when the object is
instantiated with the stack semantics.
Would the following be accurate? If I create a ref class, I should only
create a destructor for it if I plan to delete some instances of it manually
on occasion [...] or if I plan to create some instances in stack semantic form

To the best of my knowledge, if you allocate only managed memory, the GC
will take care of that, so it's not required that you call the
destructor. You must absolutely create a destructor and ensure
deterministic cleanup if you have unmanaged objects or resources inside
your ref class. Generally speaking, if an object inherits from
IDisposable (defines a destructor, in C++/CLI terms), at the minimum
you're encouraged to call the destructor. There are cases when you must
call the destructor in a deterministic manner, because you can't rely on
GC. That decision has to be made on a case-by-case basis.
That is, if all class instances will be deleted via the GC and are never
created in stack semantic form, then the destructor is just wasted code
taking up space.

No. You're encouraged to use the destructor if the class has one. The
finalizer is just a safety net, the last resort. If you could guarantee
deterministic destruction, there would be no need for a finalizer.
On the other side, if I can guarantee the instances will
always be stack semantic in form or they will always be manually deleted,
then the finalizer is wasted code taking up space.

I don't think there's too much overhead involved there. To prevent
double typing, the destructor and the finalizer can share code. You can
write a function and call that from both the d'tor and the finalizer.
You can bet that an object won't be destroyed both ways, but you have no
idea how your objects will be used. Ideally they're Disposed, sometimes
they're finalized, you really need to be prepared for both cases.
Wow! So in the effort to make things more simple and automitized we have
added a new layer of complication: the finalizer!

Look, there are cases where an automatic garbage collection works well,
and there are cases when it doesn't. There are cases when all you want
is automatic deterministic cleanup. There is such a thing, and one
possible implementation is the reference counted smart pointer
(boost::shared_ptr). It works as long as the language has stack
semantics. Unfortunately .NET doesn't always have it.

C++/CLI and C# have something that is a half-baked stack syntax. You can
create a local variable with deterministic destruction. In C# it's done
with the "using" keyword. However, stack semantics don't work well with
the current .NET collections. In a perfect world, value types would have
constructors and destructors too, so you could use stack semantics
within containers.

I'm (still) looking for a solution where I can guarantee deterministic
destruction with collections, something like List<shared_ptr<RefClass>>,
which would be equivalent to the native vector<shared_ptr<Class> >. I
don't see how it's going to happen, because I can't declare a destructor
for a value type. I'm not sure if STL.NET will bring anything to the rescue.

Those who program in 100% managed environment probably won't care too
much about it, but many of my managed classes contain lots of unmanaged
objects, so I don't have a choice but to follow deterministic
destruction practices. Right now I'm in a situation where I was before
boost::shared_ptr was released... a lot of pain and insecurity.

Again, if you don't wrap unmanaged code, but work in a fully managed
environment, this issue is not nearly as pressing.

Tom
 
P

Peter Oliphant

Hi Tom,

Good post, good explanation. But I do need to study this stuff a lot more, I
obviously have some gross misconceptions based on legacy thinking. And, btw,
nowadays I write a routine called "m_Deconstuct()" and call it in both the
destructor and finalizer (the shared code idea you mnetioned). This works in
all cases I've had, and will work in any case where one doesn't have to make
a distinction between GC and manual destruction.

To be honest, I've discovered I'm writing more classes WITHOUT the need of
any destuctor( or finalizer). Most times I write my code so the destructor
exists, but does nothing. That is, in most cases I don't really need it. The
only reason this became important to me (and why I started this topic) is
that I created a class that uses a serial port, and during it I set the baud
rate to a non-default amount. I then naturally set the baud rate back in the
destructor, only to discover it was never being called. This resulted in
future runs being affected, since they assumed the baud rate had been
returned to default (which is possibly bad programming in the first place,
but go with me on this), and it couldn't even change it back since it could
no longer talk to the chip! So, in this case, I was using the destructor for
more than just freeing up memory, but indeed had some functionality beyond.
It is in these cases one must know the rules of the game... : )

[==P==]

Tamas Demjen said:
Peter said:
This implies you can't count on either the destructor OR the finalizer
from ever being called. What you said seems to imply one of these WILL be
called. Which is true?

If GC is performed, it guarantees to call the finalizer. However, garbage
collection is not guaranteed to be done. Garbage is collected when the
system thinks it is necessary (for example, it runs out of memory).
However, when the application exits, it is not guaranteed to be called,
because the OS cleans up after each application anyway. You can try to
call GC::Collect manually.

The destructor is always guaranteed to be called when the object is
instantiated with the stack semantics.
Would the following be accurate? If I create a ref class, I should only
create a destructor for it if I plan to delete some instances of it
manually on occasion [...] or if I plan to create some instances in stack
semantic form

To the best of my knowledge, if you allocate only managed memory, the GC
will take care of that, so it's not required that you call the destructor.
You must absolutely create a destructor and ensure deterministic cleanup
if you have unmanaged objects or resources inside your ref class.
Generally speaking, if an object inherits from IDisposable (defines a
destructor, in C++/CLI terms), at the minimum you're encouraged to call
the destructor. There are cases when you must call the destructor in a
deterministic manner, because you can't rely on GC. That decision has to
be made on a case-by-case basis.
That is, if all class instances will be deleted via the GC and are never
created in stack semantic form, then the destructor is just wasted code
taking up space.

No. You're encouraged to use the destructor if the class has one. The
finalizer is just a safety net, the last resort. If you could guarantee
deterministic destruction, there would be no need for a finalizer.
On the other side, if I can guarantee the instances will always be stack
semantic in form or they will always be manually deleted, then the
finalizer is wasted code taking up space.

I don't think there's too much overhead involved there. To prevent double
typing, the destructor and the finalizer can share code. You can write a
function and call that from both the d'tor and the finalizer. You can bet
that an object won't be destroyed both ways, but you have no idea how your
objects will be used. Ideally they're Disposed, sometimes they're
finalized, you really need to be prepared for both cases.
Wow! So in the effort to make things more simple and automitized we have
added a new layer of complication: the finalizer!

Look, there are cases where an automatic garbage collection works well,
and there are cases when it doesn't. There are cases when all you want is
automatic deterministic cleanup. There is such a thing, and one possible
implementation is the reference counted smart pointer (boost::shared_ptr).
It works as long as the language has stack semantics. Unfortunately .NET
doesn't always have it.

C++/CLI and C# have something that is a half-baked stack syntax. You can
create a local variable with deterministic destruction. In C# it's done
with the "using" keyword. However, stack semantics don't work well with
the current .NET collections. In a perfect world, value types would have
constructors and destructors too, so you could use stack semantics within
containers.

I'm (still) looking for a solution where I can guarantee deterministic
destruction with collections, something like List<shared_ptr<RefClass>>,
which would be equivalent to the native vector<shared_ptr<Class> >. I
don't see how it's going to happen, because I can't declare a destructor
for a value type. I'm not sure if STL.NET will bring anything to the
rescue.

Those who program in 100% managed environment probably won't care too much
about it, but many of my managed classes contain lots of unmanaged
objects, so I don't have a choice but to follow deterministic destruction
practices. Right now I'm in a situation where I was before
boost::shared_ptr was released... a lot of pain and insecurity.

Again, if you don't wrap unmanaged code, but work in a fully managed
environment, this issue is not nearly as pressing.

Tom
 
T

Tamas Demjen

Tamas said:
I'm (still) looking for a solution where I can guarantee deterministic
destruction with collections.

Something like this actually helps a bit:

using namespace System::Collections::Generic;

template <class T>
ref class ManagedList
{
public:
~ManagedList()
{
for each(T item in items)
delete item;
}
void Add(T item)
{
items.Add(item);
}
void RemoveAt(int index)
{
delete items[index];
items.RemoveAt(index);
}
private:
List<T> items;
};

ref class Guarded
{
public:
~Guarded() { Console::WriteLine(L"~Guarded"); }
};

int main(array<System::String ^> ^args)
{
ManagedList<Guarded^> items;
items.Add(gcnew Guarded);
return 0;
}
 
A

Arnaud Debaene

Peter Oliphant wrote:
The only reason this became important to me
(and why I started this topic) is that I created a class that uses a
serial port, and during it I set the baud rate to a non-default
amount. I then naturally set the baud rate back in the destructor,
only to discover it was never being called. This resulted in future
runs being affected, since they assumed the baud rate had been
returned to default (which is possibly bad programming in the first
place, but go with me on this), and it couldn't even change it back
since it could no longer talk to the chip! So, in this case, I was
using the destructor for more than just freeing up memory, but indeed
had some functionality beyond. It is in these cases one must know the
rules of the game... : )

This (setting back an external device - the UART - to default state) is
typically the kind of "resource management" that need to be done
synchronously and that can't be handled by the GC. Other examples are mutex
release, dabase connectin close, buffer flushing to disk before exiting,
etc.... For all those things, you should use the .NET synchronous resource
maangement mechanism : IDispsoable (which, in C++/CLI, is epressed through
the destructor + stack semantic). On the other hand, the GC is perfectly OK
to manage all memory-related stuff.

As others have said, C++/CLI stack semantic is probably a step toward the
right solution, but in the current incarnation, it doesn't go far enough
because it doesn't allow (for example) to have a container being responsible
for the lifetime of the contained objects.

Arnaud
MVP - VC
 
T

Tamas Demjen

Peter said:
Good post, good explanation. But I do need to study this stuff a lot more

This link may help:
http://msdn2.microsoft.com/en-us/library/ms177197.aspx

Here's a typical pattern:

ref class T
{
public:
T()
: m(gcnew Managed),
n(new Native)
{
}
~T()
{
delete m;
this->!T(); // destructor calls finalizer
}
!T()
{
delete native;
}
private:
Managed^ m;
Native* n;
};

I assume that Managed is a class that requires destruction (is
IDisposable). As you see, the managed member is deleted from the
destructor only, while the unmanaged member is deleted from the
finalizer, which is explicitly called from the destructor.

Of course, to simplify things, you may as well use stack semantics for
the Managed member (it's impossible for Native):

ref class T
{
public:
T() : n(new Native) { }
~T() { this->!T(); }
!T() { delete native; }
private:
Managed m;
Native* n;
};

If you follow this pattern, the destructor can simply call the finalizer.

I've created a CliScopedPtr class, which is a smart pointer designed to
protect native members inside managed classes. Here's the source code:

http://tweakbits.com/CliScopedPtr.h

Using that you can completely avoid writing your own destructors and
finalizers in most cases (the compiler generates them implicitly, so
they still exist):

#include "CliSclopedPtr.h"

ref class T
{
public:
T() : n(new Native) { }
private:
Managed m;
CliScopedPtr<Native> n;
};

Although you don't see the destructor, it's still there, and when you
use the T class from C# or VB, you must call Dispose() on it, because it
encapsulates a native resource.

CliScopedPtr probably introduces a very slight overhead, but it's
negligible. It defines the -> operator, so you can use the "n" member as
a conventional pointer:

n->Func();

I didn't define the * operator, because it caused a compiler warning,
and you can always use the .get() member to gain access to the
underlying native pointer:

Native* ptr = n.get();

Tom
 
G

Gerhard Menzl

Brandon said:
This, unfortunately, will have to be a position that we agree to
disagree. Many of the design decisions within C++ are based on
historical precedent and strict maintenance to a notion of backwards
source compatibility. While I understand many of the design decisions,
I do consider many of them mistakes in hindsight.

I agree that many, if not most, shortcomings of C++ are attributable to
the strict C compatibility requirement. The awkward declaration syntax
is the first that comes to mind (which, ironically, has been taken over
by the designers of Java and C# in order to make the syntax look more
familiar to C++ programmers). Yet I don't think that Bjarne Stroustrup,
were he to design C++ anew with today's hindsight, would drop the goal
of C compatibility. Without it, the language would never have gained
enough foothold to become a major player.

As for public virtual destructors (or their opposite), the explanation
does not apply, as neither access control, nor virtual functions, nor
destructors have any precedent in C.
And while the flexibility afforded by allowing destructors to be
non-public can be convenient, it demonstrates a classic misuse in my
mind... it would be far more effective to introduce a real language
feature that allowed the desired behaviors. Because there is so much
flexibility (and in my view, misuse) of some features, it hampers the
ability to do rigid analysis of the program from both a human and
automated perspective.

I don't think this is an issue of convenience. The keyword virtual
conveys a message: this class is meant to be derived from, and the
behaviour of this function is meant to be overridden. A virtual
destructor where polymorphic deletion is not intended is a message that
conflicts with the design. Likewise, a public destructor clearly states
that any code is meant to destroy objects of this class. For the sake of
encapsulation, I always choose the most restrictive access level that is
possible. Why should I be forced to make destructors public if clients
of a class are not meant to destroy objects? Public virtual destructors
for singletons? Come on!
Everything. I speak of type safety from the "I can prove the program
mathematically obeys the separation of objects" perspective. Because
an object deleted frees memory, it allows the programmer to allocate
another object in the memory that the pointer still points to. That is
what type safety is meant to eradicate. Clearly, we all know using a
pointer after the object to which it points is deleted is a
programming error... but so is a buffer overrun. The language is not
type safe unless it can rigidly prevent that.

You seem to have evaded answering my objection. We all know that
accessing an object after it has been disposed is a programming error.
Both C# and Java allow it. Are they not type safe?
While, I'm mostly in agreement... fifty years ago, there was growing
consensus that GC was too expensive for memory. The state of the art
GC works very well for memory today. The best that is available for
other resources only does a 1-to-1 mapping to memory... which probably
isn't the most efficient way to manage scarce resources. There's still
ample amount of research to be done in this area.

Once we have got computers to figure out fully automatically when to
release which resource, we don't need programming languages anymore, I
guess. A language that prevents you from screwing up is going to be
virtually unusable. Progress means that the opportunity to make a mess
is moved to a higher level. :)
I'm going to clearly state that I am not mixing up these things... I
spend a lot of time in the design issues here, so you can at least
note that I do know something. I stand behind what I said. Shared
resources and deterministic cleanup have very few options, and
reference counting is by far the most commonly used. If there are
others, they aren't well formalized and certainly not tied to a
language level service.

I did not mean to question your competence; my apologies if I made it
sound like I did. But I stand by my opinion that in your statement, the
two concepts were certainly mixed up. Deterministic cleanup is a fairly
abstract term that includes specialized mechanisms for dealing with
cycles. It is well known that reference counting is not such a
mechanism, but that does not mean that they don't exist. GC can deal
with the *memory* hogged by cyclically dependent objects, but it
certainly cannot resolve mutual *logical* dependencies. For that you
need specialized code anyway.
Unfortunately, I have to disagree again. Maintanence of applications
that grow to millions of lines of code is dramatically more expensive
because "techniques" are not good enough for automated proofs of
program correctness. And while Standard C++ has good support for
certain ways to reliably manage resources, it fails miserably in other
areas. (Note, .NET has similar issues -- it does incredibly well in
some cases, and fails in others.)

..NET/CLR has, well, managed to largely ignore one of the most reliable
resource management features, i.e. destructors. Of course C++ is not
perfect. To my knowledge, automated proof of correctness has never been
one of its design goals. I agree that a language which emphasizes such a
feature would probably look different.

Yet somehow I don't see what this has got to do with mandatory public
virtual destructors. Removing a possibility to minimize coupling and
expressing design intentions in code is hardly going to make proofs of
correctness easier.
I recognize that this discussion really has very few options than to
diverge towards over-heated arguments. I don't really have much more
to say, but I do appreciate your candor and your passion for C++.

Your statement about Standard C++ destructors that I initially responded
to was flying in the face of C++ best practice, hence my staunch
objection. This is not a religious or emotional issue for me, and I have
certainly no inclination to get heated up about this.

I will be offline for four weeks, so in case I fail to respond to a
reply of yours, it's not out of ignorance.


--
Gerhard Menzl

#dogma int main ()

Humans may reply by replacing the thermal post part of my e-mail address
with "kapsch" and the top level domain part with "net".
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top