Jump to page: 1 2
Thread overview
RAII and Deterministic Destruction
Aug 25, 2015
Jim Hewes
Aug 26, 2015
ZombineDev
Aug 26, 2015
ZombineDev
Aug 26, 2015
ZombineDev
Aug 26, 2015
Jim Hewes
Aug 26, 2015
cym13
Aug 26, 2015
Jim Hewes
Aug 26, 2015
rsw0x
Aug 26, 2015
Mike
Aug 26, 2015
Jim Hewes
Aug 26, 2015
kink
August 25, 2015
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
August 26, 2015
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.
August 26, 2015
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.
August 26, 2015
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
August 26, 2015
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.
August 26, 2015
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/
August 26, 2015
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


August 26, 2015
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.
August 26, 2015
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
August 26, 2015
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
« First   ‹ Prev
1 2