www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - RAII and Deterministic Destruction

reply Jim Hewes <jimhewes gmail.com> writes:
Although C++ can be ugly, one reason I keep going back to it rather then 
commit more time to reference-based languages like C# is because I like 
deterministic destruction so much. My question is whether D can REALLY 
handle this or not. I've not been sure about this for some time so now 
I'm just going to come out and finally ask.

I know about this RAII section in the documentation: 
http://dlang.org/cpptod.html#raii
But I don't believe that handles all cases, such as having classes as 
member variables of other classes. (Do the members get destructors 
called too?)

Then there is std.typecons.Unique and  std.typecons.RefCounted. With 
these, can I really get deterministic destruction for all cases like I 
would in C++?

If so, it might be a good idea to emphasize this more in the 
documentation because I'd think people coming from C++ would be looking 
for this.

Jim
Aug 25 2015
next sibling parent reply "ZombineDev" <valid_email he.re> writes:
On Tuesday, 25 August 2015 at 22:35:57 UTC, Jim Hewes wrote:
 Although C++ can be ugly, one reason I keep going back to it 
 rather then commit more time to reference-based languages like 
 C# is because I like deterministic destruction so much. My 
 question is whether D can REALLY handle this or not. I've not 
 been sure about this for some time so now I'm just going to 
 come out and finally ask.

 I know about this RAII section in the documentation: 
 http://dlang.org/cpptod.html#raii
 But I don't believe that handles all cases, such as having 
 classes as member variables of other classes. (Do the members 
 get destructors called too?)

 Then there is std.typecons.Unique and  std.typecons.RefCounted. 
 With these, can I really get deterministic destruction for all 
 cases like I would in C++?

 If so, it might be a good idea to emphasize this more in the 
 documentation because I'd think people coming from C++ would be 
 looking for this.

 Jim
Structs in D behave like in C++ - they are created on the stack (unless you use new or malloc), passed by value and are automatically destroyed at the end of the scope. All their members are destroyed recursively after the user defined destructor is called. Just try it out in a toy program and see if it works as you expect.
Aug 25 2015
parent reply "ZombineDev" <valid_email he.re> writes:
On Wednesday, 26 August 2015 at 01:09:15 UTC, ZombineDev wrote:
 On Tuesday, 25 August 2015 at 22:35:57 UTC, Jim Hewes wrote:
 Although C++ can be ugly, one reason I keep going back to it 
 rather then commit more time to reference-based languages like 
 C# is because I like deterministic destruction so much. My 
 question is whether D can REALLY handle this or not. I've not 
 been sure about this for some time so now I'm just going to 
 come out and finally ask.

 I know about this RAII section in the documentation: 
 http://dlang.org/cpptod.html#raii
 But I don't believe that handles all cases, such as having 
 classes as member variables of other classes. (Do the members 
 get destructors called too?)

 Then there is std.typecons.Unique and  
 std.typecons.RefCounted. With these, can I really get 
 deterministic destruction for all cases like I would in C++?

 If so, it might be a good idea to emphasize this more in the 
 documentation because I'd think people coming from C++ would 
 be looking for this.

 Jim
Structs in D behave like in C++ - they are created on the stack (unless you use new or malloc), passed by value and are automatically destroyed at the end of the scope. All their members are destroyed recursively after the user defined destructor is called. Just try it out in a toy program and see if it works as you expect.
Classes on the other hand are reference types and are created on the garbage-collected heap by default (though you can do your own memory management). Similarly, structs created by new are also allocated on the GC heap. During collections the GC will call destructors, but it is not guaranteed and you should not rely on it. For example (depending on your GC usage and configuration) only a single collection may occur at the end of the program.
Aug 25 2015
parent reply "ZombineDev" <valid_email he.re> writes:
On Wednesday, 26 August 2015 at 01:18:43 UTC, ZombineDev wrote:
 On Wednesday, 26 August 2015 at 01:09:15 UTC, ZombineDev wrote:
 On Tuesday, 25 August 2015 at 22:35:57 UTC, Jim Hewes wrote:
 Although C++ can be ugly, one reason I keep going back to it 
 rather then commit more time to reference-based languages 
 like C# is because I like deterministic destruction so much. 
 My question is whether D can REALLY handle this or not. I've 
 not been sure about this for some time so now I'm just going 
 to come out and finally ask.

 I know about this RAII section in the documentation: 
 http://dlang.org/cpptod.html#raii
 But I don't believe that handles all cases, such as having 
 classes as member variables of other classes. (Do the members 
 get destructors called too?)

 Then there is std.typecons.Unique and  
 std.typecons.RefCounted. With these, can I really get 
 deterministic destruction for all cases like I would in C++?

 If so, it might be a good idea to emphasize this more in the 
 documentation because I'd think people coming from C++ would 
 be looking for this.

 Jim
Structs in D behave like in C++ - they are created on the stack (unless you use new or malloc), passed by value and are automatically destroyed at the end of the scope. All their members are destroyed recursively after the user defined destructor is called. Just try it out in a toy program and see if it works as you expect.
Classes on the other hand are reference types and are created on the garbage-collected heap by default (though you can do your own memory management). Similarly, structs created by new are also allocated on the GC heap. During collections the GC will call destructors, but it is not guaranteed and you should not rely on it. For example (depending on your GC usage and configuration) only a single collection may occur at the end of the program.
Generally a very tutorial and reference is Ali's book [1], which is listed under Books and Articles in the sidebar of dlang.org. You should check the chapters Constructor and Other Special Functions [2] and Memory Management [3] (even though it covers mostly the GC). If you wish to avoid the GC, you can annotate your functions with the nogc attribute [4] which enforces at compile-time that the annotated function won't use the GC. It is transitive which means that if you annotate your main() function with it, you shouldn't be allowed to use anything that allocates memory from the GC in your whole program. Since this feature was added there is an ongoing effort to minimize GC usage in the standard library, but there's still stuff that require it. That said the recommended approach is to build your data processing on ranges [5] (see the linked article by Andrei Alexandrescu) because it allows you to encapsulate memory usage more strategically. This is enabled by the lazy nature of ranges - they process data only when needed and so they don't need to allocate memory. Probably the best introduction to this technique is Walter Bright's keynote at DConf 2015 [6]: https://www.youtube.com/watch?v=znjesAXEEqw Be sure to check the DConf website as there's lots of great content from 2013 [7], 2014 [8] and 2015 [9]. [1]: http://ddili.org/ders/d.en/index.html [2]: http://ddili.org/ders/d.en/special_functions.html [3]: http://ddili.org/ders/d.en/memory.html [4]: http://dlang.org/attribute.html#nogc [5]: http://dlang.org/phobos/std_range.html [6]: https://www.youtube.com/watch?v=znjesAXEEqw [7]: http://dconf.org/2013/ [8]: http://dconf.org/2014/ [9]: http://dconf.org/2015/
Aug 25 2015
parent Jim Hewes <jimhewes gmail.com> writes:
Thanks for the thorough response. I'm aware of some of what you 
explained. Maybe I should have asked differently. Rather than asking 
what RAII facilities do exist, I guess I was looking for the answer, 
"Here's what you typically do in C++ RAII that you can't do in D." I 
could probably find out things by experimenting too (and not be too 
lazy). I just didn't want to rely on my assumptions only.

For example, say object A is a member of object B which is in turn a 
member of object C. If C is deleted or goes out of scope, does the 
destructor of A get called? If all three are classes, obviously not. But 
if all three are structs? What if they are classes but are all managed 
by Unique? If I use Unique for all of my heap-allocated classes (as I 
would with std::unique_ptr in C++)  am I assured of destructors being 
called when the owning classes get destructed? I'm wondering about these 
various nesting/owning combinations.

Jim
Aug 26 2015
prev sibling next sibling parent reply "cym13" <cpicard openmailbox.org> writes:
On Tuesday, 25 August 2015 at 22:35:57 UTC, Jim Hewes wrote:
 Although C++ can be ugly, one reason I keep going back to it 
 rather then commit more time to reference-based languages like 
 C# is because I like deterministic destruction so much. My 
 question is whether D can REALLY handle this or not. I've not 
 been sure about this for some time so now I'm just going to 
 come out and finally ask.

 I know about this RAII section in the documentation: 
 http://dlang.org/cpptod.html#raii
 But I don't believe that handles all cases, such as having 
 classes as member variables of other classes. (Do the members 
 get destructors called too?)

 Then there is std.typecons.Unique and  std.typecons.RefCounted. 
 With these, can I really get deterministic destruction for all 
 cases like I would in C++?

 If so, it might be a good idea to emphasize this more in the 
 documentation because I'd think people coming from C++ would be 
 looking for this.

 Jim
Hi Jim. RAII in D is a common issue for people coming from C++. I don't know everything on the subject and would be glad to be corrected if I write a mistake but here is my share on the subject. There are two very different kind of objects in D and memory management is different for each: structs and classes. I think you mostly want to hear about classes, but I think each should be studied nonetheless. Structs: ===== Why would you use structs? - They are stack-allocated by default (but can be heap-allocated at will) - They are destroyed at the end of the scope, hence supporting RAII properly - They provide rather nice idioms for RAII [1] - They support single-inheritance through “alias this” - They support compile-time polymorphism through templates - They support complex object composition through template mixins [2] Why wouldn't you use structs? - You want to plug yourself on an already existing class-based system - You want to use interfaces - You need runtime-polymorphism Classes: ===== Why would you use classes? - They are heap-based and managed by the GC: lazy destruction - They provide full support for inheritance and OOP (including interfaces) - They support compile-time polymorphism through templates - They support complex object composition through template mixins [2] Why wouldn't use classes? - They are heap-based - They don't allow deterministic implicit destruction How to solve these problems? ================== Classes don't have *implicit* deterministic destruction but nothing prevents you from calling a destructor explicitely (be it ~this or another dedicated function). This can be easily done using scope(...) statements. If this is cumbersome, std.typecons.Unique and std.typecons.RefCounted can be used to provide a comportment analogous to that of modern C++. We said that structs are scope-based, a solution is to wrap an object into a struct and set the structs destructor to destroy the object as well at the end of the scope. std.typecons.scoped provides a nice wrapper arround this behaviour. The destruction of an object doesn't mean the destruction of the objects that it referenced. That can also be done explicitely. A word on the GC =========== I see a lot of C++ programmer who come to D with a single thought in mind: “Avoid the GC at all cost, let me manage my damn memory”. This is understandable, yet biaised. True, a GC isn't for all applications, but it has its place. Instead of fearing it, learning to use it well is important. One often doesn't need deterministic destruction. Lazy destruction has its benefits. I strongly advise not to rush trying to avoid the GC, instead profile profile profile. Conclusion ======= Doing RAII is possible for structs and for classes, but it is *really* easier with structs. D structs are really powerful and often underestimated by newcommers who rush on the "class" keyword. D classes aren't C++ classes. My advice would be to use structs as much as possible, to use classes where needed relying on the GC, and after profiling to introduce RAII behaviour into problematic classes. [1] https://w0rp.com/blog/post/an-raii-constructor-by-another-name-is-just-as-sweet/ [2] https://blog.dicebot.lv/posts/2015/08/OOP_composition_with_mixins
Aug 25 2015
parent Jim Hewes <jimhewes gmail.com> writes:
Thanks for all the info. It's a good comparison of structs and classes 
to keep handy. Actually, I'm fine with the GC. I don't mean to avoid it. 
I just would like some way to also have non-memory resources 
automatically released in a timely, predictable way.

One common thing to do in C++ is to manage the lifetime of an object 
with std::unique_ptr but then use its .get() function to get a native 
pointer to use temporarily. You just have to ensure that the native 
pointer doesn't outlive the unique_ptr, which isn't that difficult. 
True, if the unique_ptr gets destructed the native pointer is invalid 
and that could be risky, but you limit its use.
Unique in D doesn't seem to have anything like a get() function but I 
wonder if it could. That is, it would get another "native" reference to 
the Unique resource that is managed by the garbage collector. So if the 
Unique went out of scope and got destructed, the reference would at 
least refer to valid memory although not a valid object because its 
destructor had already been called. Not perfectly safe, but no worse 
than the C++ case. Just a thought.

Jim
Aug 26 2015
prev sibling next sibling parent "rsw0x" <anonymous anonymous.com> writes:
On Tuesday, 25 August 2015 at 22:35:57 UTC, Jim Hewes wrote:
 Although C++ can be ugly, one reason I keep going back to it 
 rather then commit more time to reference-based languages like 
 C# is because I like deterministic destruction so much. My 
 question is whether D can REALLY handle this or not. I've not 
 been sure about this for some time so now I'm just going to 
 come out and finally ask.

 I know about this RAII section in the documentation: 
 http://dlang.org/cpptod.html#raii
 But I don't believe that handles all cases, such as having 
 classes as member variables of other classes. (Do the members 
 get destructors called too?)

 Then there is std.typecons.Unique and  std.typecons.RefCounted. 
 With these, can I really get deterministic destruction for all 
 cases like I would in C++?

 If so, it might be a good idea to emphasize this more in the 
 documentation because I'd think people coming from C++ would be 
 looking for this.

 Jim
To add to what the other people said, there exists scoped!T in std.typecons to allocate a class on the stack, and Unique/RefCounted as you mentioned. AFAIK refcounted is in the process of being overhauled, but the user should notice no differences.
Aug 25 2015
prev sibling next sibling parent reply "Mike" <none none.com> writes:
On Tuesday, 25 August 2015 at 22:35:57 UTC, Jim Hewes wrote:
 Although C++ can be ugly, one reason I keep going back to it 
 rather then commit more time to reference-based languages like 
 C# is because I like deterministic destruction so much. My 
 question is whether D can REALLY handle this or not. I've not 
 been sure about this for some time so now I'm just going to 
 come out and finally ask.
You may also find http://wiki.dlang.org/Memory_Management useful. It shows a number of different patterns one can employ for deterministic memory management. The one which you'll probably find most C++-like is http://wiki.dlang.org/Memory_Management#Explicit_Class_Instance_Allocation Mike
Aug 25 2015
parent Jim Hewes <jimhewes gmail.com> writes:
Thanks. I had not looked at some of those yet.

Jim
Aug 26 2015
prev sibling parent "kink" <noone nowhere.com> writes:
A serious bug affecting RAII is 
https://issues.dlang.org/show_bug.cgi?id=14903, but apparently 
its importance hasn't been properly acknowledged yet. Improving 
the performance of binaries produced by dmd's backend is 
obviously way more important than fixing serious bugs or 
commenting on related PRs.
Aug 26 2015