www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - How to call destructor before free without dropping nogc?

reply Bienlein <ffm2002 web.de> writes:
Hello,

I allocate some instance of class C manually and then free the 
memory again:

     class C {
         int num;

         ~this() {
     	writeln("~this");
         }
     }

     void foo() //  nogc
     {
         auto mem = cast(C)malloc(__traits(classInstanceSize, C));
         auto c = emplace!(C)(mem);

         c.num = 789;

         destroy(c);
         free(cast(void*) c);
         c = null;
     }

     int main()
     {
     	foo();
     }

The code above works well as the destructor of c in class C is 
called by destroy. Problem is that destroy cannot be used once 
function foo is annotated with  nogc. There seems to be no way 
round it.

What I want is to keep the function foo annotated with  nogc, but 
still have the destructor of C be called before free is called. 
Is there a way to call the destructor through meta programming or 
some kind of reflection so that I can create some generic 
function that calls the destructor and then free for any kind of 
class?

Thanks, Bienlein
Aug 19 2021
next sibling parent reply Bienlein <ffm2002 web.de> writes:
On Thursday, 19 August 2021 at 07:30:38 UTC, Bienlein wrote:
 Hello,

 I allocate some instance of class C manually and then free the 
 memory again:

     class C {
         int num;

         ~this() {
     	writeln("~this");
         }
     }

     void foo() //  nogc
     {
         auto mem = cast(C)malloc(__traits(classInstanceSize, 
 C));
         auto c = emplace!(C)(mem);

         c.num = 789;

         destroy(c);
         free(cast(void*) c);
         c = null;
     }

     int main()
     {
     	foo();
     }

 The code above works well as the destructor of c in class C is 
 called by destroy. Problem is that destroy cannot be used once 
 function foo is annotated with  nogc. There seems to be no way 
 round it.

 What I want is to keep the function foo annotated with  nogc, 
 but still have the destructor of C be called before free is 
 called. Is there a way to call the destructor through meta 
 programming or some kind of reflection so that I can create 
 some generic function that calls the destructor and then free 
 for any kind of class?

 Thanks, Bienlein
Oops, I just realized that you can also not call emplace when nogc is present. Well that is at least consistent with not either being able to call destroy ;-). So, I guess this means that you can forget about manually allocating and freeing some instance of a class and using nogc as well. That's a pitty, nogc was a good idea.
Aug 19 2021
next sibling parent reply vit <vit vit.vit> writes:
On Thursday, 19 August 2021 at 08:25:23 UTC, Bienlein wrote:
 On Thursday, 19 August 2021 at 07:30:38 UTC, Bienlein wrote:
 Hello,

 I allocate some instance of class C manually and then free the 
 memory again:

     class C {
         int num;

         ~this() {
     	writeln("~this");
         }
     }

     void foo() //  nogc
     {
         auto mem = cast(C)malloc(__traits(classInstanceSize, 
 C));
         auto c = emplace!(C)(mem);

         c.num = 789;

         destroy(c);
         free(cast(void*) c);
         c = null;
     }

     int main()
     {
     	foo();
     }

 The code above works well as the destructor of c in class C is 
 called by destroy. Problem is that destroy cannot be used once 
 function foo is annotated with  nogc. There seems to be no way 
 round it.

 What I want is to keep the function foo annotated with  nogc, 
 but still have the destructor of C be called before free is 
 called. Is there a way to call the destructor through meta 
 programming or some kind of reflection so that I can create 
 some generic function that calls the destructor and then free 
 for any kind of class?

 Thanks, Bienlein
Oops, I just realized that you can also not call emplace when nogc is present. Well that is at least consistent with not either being able to call destroy ;-). So, I guess this means that you can forget about manually allocating and freeing some instance of a class and using nogc as well. That's a pitty, nogc was a good idea.
Try this: ```d import std; import core.stdc.stdlib : malloc, free; class C { int num; ~this() nogc{ debug writeln("~this"); } } void foo() nogc { auto mem = cast(C)malloc(__traits(classInstanceSize, C)); auto c = emplace!(C)(mem); c.num = 789; destruct(c); free(cast(void*) c); c = null; } void main() { foo(); } //https://github.com/atilaneves/automem/blob/master/source/automem/utils.d void destruct(T)(T obj) if (is(T == class)) { (cast(_finalizeType!T) &rt_finalize)(() trusted { return cast(void*) obj; }()); } private extern(C){ void rt_finalize(void* p, bool det = true) trusted nogc pure nothrow; template _finalizeType(T) { import std.traits: Unqual; static if (is(Unqual!T == Object)) { alias _finalizeType = typeof(&rt_finalize); } else { import std.traits : BaseClassesTuple; import std.meta : AliasSeq; alias _finalizeType = typeof(function void(void* p, bool det = true) { // generate a body that calls all the destructors in the chain, // compiler should infer the intersection of attributes foreach (B; AliasSeq!(T, BaseClassesTuple!T)) { // __dtor, i.e. B.~this static if (__traits(hasMember, B, "__dtor")) () { B obj; obj.__dtor; } (); // __xdtor, i.e. dtors for all RAII members static if (__traits(hasMember, B, "__xdtor")) () { B obj; obj.__xdtor; } (); } }); } } } ```
Aug 19 2021
parent Bienlein <jeti789 web.de> writes:
This works, vit. Thanks! I thought it wouldn't, because your code 
still makes use of embrace. But it somehow worked, although I 
don't understand why ... ;-).

I also added a constructor using the same approach as your 
destructor and this also worked:

      this(int otherNum)  nogc {
          this.num = otherNum;
          debug writeln("this: ", this.num);
      }

 evilrat: Will try what you suggested after work today. Too busy 
now.
Aug 19 2021
prev sibling parent reply evilrat <evilrat666 gmail.com> writes:
On Thursday, 19 August 2021 at 08:25:23 UTC, Bienlein wrote:
 Oops, I just realized that you can also not call emplace when 
  nogc is present. Well that is at least consistent with not 
 either being able to call destroy ;-).

 So, I guess this means that you can forget about manually 
 allocating and freeing some instance of a class and using  nogc 
 as well. That's a pitty,  nogc was a good idea.
you are probably doing something wrong, could you try nogc ctor? anyway nogc is way too limiting, I don't see why bother when there is already `scope` storage (should work in nogc) and -vgc flag to show possible allocations. ```d import core.lifetime; import core.stdc.stdio; import core.stdc.stdlib; class SomeClass { int a = 42; this() nogc { } this(int val) nogc { a = val; } } nogc void main() { byte[64] mem; mem.emplace!SomeClass(); printf("stack %d\n", (cast(SomeClass) mem.ptr).a); // 42 scope a = new SomeClass(); printf("scope %d\n", a.a); //42 SomeClass dynAlloc = cast(SomeClass) malloc(__traits(classInstanceSize, SomeClass)); dynAlloc = emplace!SomeClass(dynAlloc, 123); printf("dynamic %d\n", dynAlloc.a); // 123 } ```
Aug 19 2021
parent Tejas <notrealemail gmail.com> writes:
On Thursday, 19 August 2021 at 09:39:26 UTC, evilrat wrote:
 On Thursday, 19 August 2021 at 08:25:23 UTC, Bienlein wrote:
 Oops, I just realized that you can also not call emplace when 
  nogc is present. Well that is at least consistent with not 
 either being able to call destroy ;-).

 So, I guess this means that you can forget about manually 
 allocating and freeing some instance of a class and using 
  nogc as well. That's a pitty,  nogc was a good idea.
you are probably doing something wrong, could you try nogc ctor? anyway nogc is way too limiting, I don't see why bother when there is already `scope` storage (should work in nogc) and -vgc flag to show possible allocations. ```d import core.lifetime; import core.stdc.stdio; import core.stdc.stdlib; class SomeClass { int a = 42; this() nogc { } this(int val) nogc { a = val; } } nogc void main() { byte[64] mem; mem.emplace!SomeClass(); printf("stack %d\n", (cast(SomeClass) mem.ptr).a); // 42 scope a = new SomeClass(); printf("scope %d\n", a.a); //42 SomeClass dynAlloc = cast(SomeClass) malloc(__traits(classInstanceSize, SomeClass)); dynAlloc = emplace!SomeClass(dynAlloc, 123); printf("dynamic %d\n", dynAlloc.a); // 123 } ```
Allocating to a function local variable via ```new``` always allocates on stack assuming no arguments are passed to new Read sentence 6 of https://dlang.org/spec/expression.html#new_expressions So ```d scope a = new SomeClass(); ``` actually allocates on stack
Aug 19 2021
prev sibling parent reply Ferhat =?UTF-8?B?S3VydHVsbXXFnw==?= <aferust gmail.com> writes:
On Thursday, 19 August 2021 at 07:30:38 UTC, Bienlein wrote:
 Hello,

 I allocate some instance of class C manually and then free the 
 memory again:

 [...]
I just wanted to leave this here. https://github.com/AuburnSounds/Dplug/blob/master/core/dplug/core/nogc.d
Aug 19 2021
parent reply evilrat <evilrat666 gmail.com> writes:
On Thursday, 19 August 2021 at 15:12:03 UTC, Ferhat Kurtulmuş 
wrote:
 On Thursday, 19 August 2021 at 07:30:38 UTC, Bienlein wrote:
 Hello,

 I allocate some instance of class C manually and then free the 
 memory again:

 [...]
I just wanted to leave this here. https://github.com/AuburnSounds/Dplug/blob/master/core/dplug/core/nogc.d
This is cool, but even in unit tests for malloc wrapper there is only simple case with class without references to another class and no dtor. Seems like the issue is that one have to add nogc constructor/destructor overloads for emplace/destroy, and the author can't have nogc dtor because of writeln (IIRC nogc using GC is allowed with `debug` anyway), and all class members of another classes must recursively provide them as well.
Aug 19 2021
next sibling parent Ferhat =?UTF-8?B?S3VydHVsbXXFnw==?= <aferust gmail.com> writes:
On Thursday, 19 August 2021 at 15:38:19 UTC, evilrat wrote:
 On Thursday, 19 August 2021 at 15:12:03 UTC, Ferhat Kurtulmuş
 This is cool, but even in unit tests for malloc wrapper there 
 is only simple case with class without references to another 
 class and no dtor.
If you examine the entire library, there are various use cases of nogc classes. For instance, a derived class containing references to other class objects [1]. I am not using classes heavily with D. I just once happily used dplug's nogc facilities. When I saw this thread, I just wanted to share it here.
 Seems like the issue is that one have to add  nogc 
 constructor/destructor overloads for emplace/destroy, and the 
 author can't have  nogc dtor because of writeln (IIRC  nogc 
 using GC is allowed with `debug` anyway), and all class members 
 of another classes must recursively provide them as well.
I agree with you. D needs more nogc facilities for OOP. It would not be so hard to include those overloads. Probably, this would violate the strictly defended safety principles of D? [1]: https://github.com/AuburnSounds/Dplug/blob/f67c14fd5ba44225d6669e87f942d641c8bf8ab8/window/dplug/window/cocoawindow.d
Aug 19 2021
prev sibling parent Ferhat =?UTF-8?B?S3VydHVsbXXFnw==?= <aferust gmail.com> writes:
On Thursday, 19 August 2021 at 15:38:19 UTC, evilrat wrote:
 On Thursday, 19 August 2021 at 15:12:03 UTC, Ferhat Kurtulmuş
Btw, based on https://github.com/dlang/druntime/blob/master/src/object.d#L4209: import core.lifetime; import core.stdc.stdio; import core.stdc.stdlib; extern (C) void rt_finalize(void *data, bool det=true) nogc nothrow; // cheap hack here alias destroy = rt_finalize; class SomeClass { int a = 42; this() nogc { } ~this() nogc {printf("nogc\n");} this(int val) nogc { a = val; } } nogc void main() { SomeClass dynAlloc = cast(SomeClass) malloc(__traits(classInstanceSize, SomeClass)); dynAlloc = emplace!SomeClass(dynAlloc, 123); printf("dynamic %d\n", dynAlloc.a); // 123 //rt_finalize(cast(void*)dynAlloc); destroy(cast(void*)dynAlloc); // cast needed :/ dunno consequences though }
Aug 19 2021