www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - class destruction

reply "Q" <a b.de> writes:
Hi. I'm playing around with D for a while and I would like to 
switch. But here is one thing, I need an answer for. In the Docs 
is mentioned that it is not sure that the DTor of a class is 
called. But what if I have struct, which holds a C Handle which 
is destroyed as soon as the struct gets destroyed (more or less 
like a unique pointer) and I stick this struct into a class 
(because I strongly need polymorphism)? Can I be sure that the 
Handle is destroyed as soon as the class is destroyed? Or will 
the handle leak?
If so there are only two ways that come to mind:
1.  the class holds only a pointer to the struct, which is unsafe 
since I must guarantee that the struct lives as long as the class
2. the class gets a close/finalize/destroy method (or is called 
with the built in destroy method), which is a absolute nogo, 
because it is absolutly sure that this can be forgotten. Besides, 
we live in 2015 and that is not C where I have to clean my code 
manually, so this option would be ridiculous :D

I'm gratefull for any answers and ideas.
Sep 09 2015
next sibling parent reply "ponce" <contact gam3sfrommars.fr> writes:
On Wednesday, 9 September 2015 at 07:19:58 UTC, Q wrote:
 Hi. I'm playing around with D for a while and I would like to 
 switch. But here is one thing, I need an answer for. In the 
 Docs is mentioned that it is not sure that the DTor of a class 
 is called. But what if I have struct, which holds a C Handle 
 which is destroyed as soon as the struct gets destroyed (more 
 or less like a unique pointer) and I stick this struct into a 
 class (because I strongly need polymorphism)?
Then you have to make sure the class destruction happens and not rely on the GC.
 Can I be sure that the Handle is destroyed as soon as the class 
 is destroyed?
I think that yes, struct members are destroyed. But you need to make sure the class is destroyed.
 If so there are only two ways that come to mind:
 1.  the class holds only a pointer to the struct, which is 
 unsafe since I must guarantee that the struct lives as long as 
 the class
 2. the class gets a close/finalize/destroy method (or is called 
 with the built in destroy method), which is a absolute nogo, 
 because it is absolutly sure that this can be forgotten. 
 Besides, we live in 2015 and that is not C where I have to 
 clean my code manually, so this option would be ridiculous :D

 I'm gratefull for any answers and ideas.
I'm using the manual method after much hair-pulling: http://p0nce.github.io/d-idioms/#GC-proof-resource-class It absolutely is worse than the C++ situation, however with the above pattern leaks will be reported by the GC, which is a nice consolation prize. Alternatively, stick your class in Unique! / Refcounted! / scoped!
Sep 09 2015
parent reply "Q" <a b.de> writes:
But if others use my code they must think about finalizing the 
classes? That is very awkward.
I'll take a look at Rust and otherwise I will stick with C++. 
Thanks for you answer. :)
Sep 09 2015
parent "ponce" <contact gam3sfrommars.fr> writes:
On Wednesday, 9 September 2015 at 14:36:24 UTC, Q wrote:
 But if others use my code they must think about finalizing the 
 classes? That is very awkward.
classes, yes but structs, no.
 I'll take a look at Rust and otherwise I will stick with C++. 
 Thanks for you answer. :)
Well, resource management is only part of the story.
Sep 09 2015
prev sibling next sibling parent "Kagamin" <spam here.lot> writes:
On Wednesday, 9 September 2015 at 07:19:58 UTC, Q wrote:
 Besides, we live in 2015 and that is not C where I have to 
 clean my code manually, so this option would be ridiculous :D
The difference is that in C you need to manage POD memory too. Manual management of non-POD resources is a problem of a smaller size, and you can use RAII too.
Sep 09 2015
prev sibling parent reply "Adam D. Ruppe" <destructionator gmail.com> writes:
On Wednesday, 9 September 2015 at 07:19:58 UTC, Q wrote:
 Can I be sure that the Handle is destroyed as soon as the class 
 is destroyed?
It will do that automatically. Like the others said, you won't be sure when the class is destroyed unless you have the user code take ownership of it. (This btw is the same as C++, just in C++ the ownership syntax is a wee bit shorter.) C++: class Foo { public: Foo() { /* acquire */ } ~Foo() { /* destroy */ } }; void useFoo(Foo* foo) { } // pretend I did the header separation here int main() { Foo foo; useFoo(&foo); return 0; } D: -- module foo; class Foo { private this() { /* acquire */ } ~this() { /* destroy */ } } struct OwnedFoo(F) { F foo; alias foo this; ~this() { .destroy(foo); } disable this(this) {} } auto create(F, T...)(T t) if(is(F : Foo)) { return OwnedFoo!F(new F(t)); } -- -- module main; import foo; void useFoo(Foo foo) {} void main() { auto foo = create!Foo(); } --- The code is a bit longer since the owned pointer in the standard library isn't exactly what I wanted, but you could use std.typecons.Unique too. (Actually, it does offer a bit more safety in reference escaping than mine. But mine is the closest to C++ default). The private constructor means it won't let you bypass the owned factory when creating it. A bit extra code in foo means all uses of it will be simple like in main. Just don't store the reference after it is destroyed, just like you would need to be careful with in C++. Using the library Unique will help you get this right too if you want to learn that.
Sep 09 2015
parent reply "Q" <a b.de> writes:
On Wednesday, 9 September 2015 at 14:57:26 UTC, Adam D. Ruppe 
wrote:
 On Wednesday, 9 September 2015 at 07:19:58 UTC, Q wrote:
 Can I be sure that the Handle is destroyed as soon as the 
 class is destroyed?
It will do that automatically. Like the others said, you won't be sure when the class is destroyed unless you have the user code take ownership of it. (This btw is the same as C++, just in C++ the ownership syntax is a wee bit shorter.) C++: class Foo { public: Foo() { /* acquire */ } ~Foo() { /* destroy */ } }; void useFoo(Foo* foo) { } // pretend I did the header separation here int main() { Foo foo; useFoo(&foo); return 0; } D: -- module foo; class Foo { private this() { /* acquire */ } ~this() { /* destroy */ } } struct OwnedFoo(F) { F foo; alias foo this; ~this() { .destroy(foo); } disable this(this) {} } auto create(F, T...)(T t) if(is(F : Foo)) { return OwnedFoo!F(new F(t)); } -- -- module main; import foo; void useFoo(Foo foo) {} void main() { auto foo = create!Foo(); } --- The code is a bit longer since the owned pointer in the standard library isn't exactly what I wanted, but you could use std.typecons.Unique too. (Actually, it does offer a bit more safety in reference escaping than mine. But mine is the closest to C++ default). The private constructor means it won't let you bypass the owned factory when creating it. A bit extra code in foo means all uses of it will be simple like in main. Just don't store the reference after it is destroyed, just like you would need to be careful with in C++. Using the library Unique will help you get this right too if you want to learn that.
But in C++, classes and structs are value types _and_ extendable.There is no pressure to heap allocate a class. Of course, if I do that, I use a unique_ptr (shared_ptr is almost everytime misplaced). But since D has a GC and (per default) force to heap allocate a class. So IMO the GC should also destroy it, everything else is just awkward. I don't want to hack around in a new language. I think Rust offers my all I want. But thanks for the explanation.
Sep 09 2015
next sibling parent "Q" <a b.de> writes:
And sorry if that sounds rude, I'm just in a hurry. I just think 
D is not mature enough for serious stuff. :) That is of course 
only my personal opinion.
Sep 09 2015
prev sibling parent reply "Adam D. Ruppe" <destructionator gmail.com> writes:
On Wednesday, 9 September 2015 at 15:10:33 UTC, Q wrote:
 But since D has a GC and (per default) force to heap allocate a 
 class. So IMO the GC should also destroy it, everything else is 
 just awkward.
Well, it *does* by default. If that's what you want, just do it the plain way with simple `new`. It will be destroyed (and any structs inside also destroyed) when the GC gets it.
Sep 09 2015
parent reply "Q" <a b.de> writes:
On Wednesday, 9 September 2015 at 15:19:04 UTC, Adam D. Ruppe 
wrote:
 On Wednesday, 9 September 2015 at 15:10:33 UTC, Q wrote:
 But since D has a GC and (per default) force to heap allocate 
 a class. So IMO the GC should also destroy it, everything else 
 is just awkward.
Well, it *does* by default. If that's what you want, just do it the plain way with simple `new`. It will be destroyed (and any structs inside also destroyed) when the GC gets it.
I thought that is not guaranteed, according to the docs?
Sep 09 2015
parent reply "Adam D. Ruppe" <destructionator gmail.com> writes:
On Wednesday, 9 September 2015 at 15:24:57 UTC, Q wrote:
 I thought that is not guaranteed, according to the docs?
It is possible that the GC will never actually run, but you can force it to if you need it to by calling GC.collect at any time.
Sep 09 2015
parent reply "Q" <a b.de> writes:
On Wednesday, 9 September 2015 at 15:32:58 UTC, Adam D. Ruppe 
wrote:
 On Wednesday, 9 September 2015 at 15:24:57 UTC, Q wrote:
 I thought that is not guaranteed, according to the docs?
It is possible that the GC will never actually run, but you can force it to if you need it to by calling GC.collect at any time.
Yes, but according to the specs it is not guaranteed that the GC calls the DTor if the Object is collected.
Sep 09 2015
next sibling parent "Adam D. Ruppe" <destructionator gmail.com> writes:
On Wednesday, 9 September 2015 at 15:37:50 UTC, Q wrote:
 Yes, but according to the specs it is not guaranteed that the 
 GC calls the DTor if the Object is collected.
Where? This page says pretty plainly: http://dlang.org/class.html#destructors "The garbage collector calls the destructor function when the object is deleted." (this is arguably a mistake, dtor and finalizer being the same thing have been a problem before, but it does say that) This sentence: "The garbage collector is not guaranteed to run the destructor for all unreferenced objects. " is because the GC is conservative and has a few exceptions in what it covers. But if you aren't one of those exceptions (data segment, or pinned by a false pointer (very rare in 64 bit btw), it will be collected and if it is collected, the dtor is run.
Sep 09 2015
prev sibling parent "Meta" <jared771 gmail.com> writes:
On Wednesday, 9 September 2015 at 15:37:50 UTC, Q wrote:
 Yes, but according to the specs it is not guaranteed that the 
 GC calls the DTor if the Object is collected.
Note that I believe this is the same as in Java (though not C#, interestingly enough). In Java an object's destructor is not guaranteed to run.
Sep 09 2015