www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Is it possible to avoid call to destructor for structs?

reply Haridas <haridas systemc.net> writes:
In the following code, Bar is an element of struct Foo. Is there 
a way to avoid a call to ~Bar when ~Foo is getting executed?

// >>>>>>>>>>

import std.stdio;

struct Foo {
   Bar bar;
   ~this() {
     writeln("~Foo");
     // some code that disables call to ~Bar
   }
}

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

void main() {
   Foo foo;
}
Sep 24
next sibling parent reply Adam D. Ruppe <destructionator gmail.com> writes:
On Sunday, 24 September 2017 at 17:11:26 UTC, Haridas wrote:
 In the following code, Bar is an element of struct Foo. Is 
 there a way to avoid a call to ~Bar when ~Foo is getting 
 executed?
No, but you could just set a flag in Bar that the destructor checks and skips running if it is set. In fact, the destructor should do nothing if bar is in its initial state, so that's how you could check it. For example ~this() { if(this.member is null) return; // finalize this.member }
Sep 24
parent reply Haridas <haridas systemc.net> writes:
Thanks Adam

Actually Bar's API and implementation is not in my control. 
Consider the scenario where it is implemented in a library. Or a 
scenario where I have millions of instances of Bar (not 
necessarily as a component of Foo) and I do not want to add to 
runtime memory footprint.

Ok, consider the following code. Now I am (seemingly) avoiding a 
call to Bar's destructor by way of using pointers. But I have 
doubts if delete on void pointer would reclaim the memory that 
has been allocated while still not calling the destructor?

// >>>>
import std.stdio;

struct Foo {
   Bar* bar;
   this(size_t l) {
     bar = cast(Bar*) new Bar[l];
   }
   ~this() {
     writeln("~Foo");
     void *tmp = cast(void*) bar;
     // would this reclaim memory
     // allocated to Bar*
     delete(tmp);
   }
}

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

void main() {
   Foo foo = 4;
}
Sep 24
parent reply Haridas <haridas systemc.net> writes:
Also consider the following code. Please let me know if I am 
doing the right thing for dynamic arrays. My hack seems to have 
the desired effect on shutting down the destructor. Is this hack 
legal use of D? Can you please guide me if/how it can be achieved 
for std.container.Array?

// >>>>
import std.stdio;

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

void main() {
   {				// dynamic array
     Bar[] bars;
     bars.length = 4;
     void* tmp = bars.ptr;
     delete(tmp);
     bars.length = 0;
   }
   {				// std.container.Array
     import std.container: Array;
     Array!Bar bars;
     bars.length = 6;
     // does not seem to work
     void* tmp = &(bars[0]);
     delete(tmp);
     bars.length = 0;
   }
}
Sep 24
parent reply Biotronic <simen.kjaras gmail.com> writes:
On Sunday, 24 September 2017 at 18:46:15 UTC, Haridas wrote:
 Also consider the following code. Please let me know if I am 
 doing the right thing for dynamic arrays. My hack seems to have 
 the desired effect on shutting down the destructor. Is this 
 hack legal use of D? Can you please guide me if/how it can be 
 achieved for std.container.Array?

 // >>>>
 import std.stdio;

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

 void main() {
   {				// dynamic array
     Bar[] bars;
     bars.length = 4;
     void* tmp = bars.ptr;
     delete(tmp);
     bars.length = 0;
   }
   {				// std.container.Array
     import std.container: Array;
     Array!Bar bars;
     bars.length = 6;
     // does not seem to work
     void* tmp = &(bars[0]);
     delete(tmp);
     bars.length = 0;
   }
 }
Since you're deleting the memory the dynamic array is pointing to, what you're doing is potentially unsafe - if anyone touches that memory after it's been deleted, nasal demons may follow. What you want is something like this: import std.stdio; struct Bar { this(int n) {} ~this() { writeln("~Bar"); } } struct SuppressGC(T) { // Disguise T as a humble array. private ubyte[T.sizeof] _payload; // Create from instance of T. this(T arg) { _payload = *cast(ubyte[T.sizeof]*)&arg; } // Or forward constructor arguments to T's constructor. static if (__traits(hasMember, T, "__ctor")) { this(Args...)(Args args) if (__traits(compiles, (Args e){__traits(getMember, T.init, "__ctor")(e);})) { __traits(getMember, get, "__ctor")(args); } } // Pretend to be a T. property ref T get() { return *cast(T*)_payload.ptr; } alias get this; } void useBar(ref Bar b) {} unittest { // Construct from instance. //This creates a temporary on the stack, and its destructor will be called. SuppressGC!Bar a = Bar(3); // Or by forwarding constructor arguments. // This constructs in-place inside SuppressGC, and no destructor will be called. auto b = SuppressGC!Bar(3); SuppressGC!Bar[] arr; arr.length = 3; // Another stack temporary. Destructor will be called. arr[0] = Bar(5); // No temp arr[1] = SuppressGC!Bar(5); // It even pretends to be the wrapped struct: useBar(b); } In general, of course, this is a bad idea - there's probably a reason that destructor does the thing it's doing. If you're sure skipping it is what you want, go ahead. -- Biotronic
Sep 24
parent reply Haridas <haridas systemc.net> writes:
 In general, of course, this is a bad idea - there's probably a 
 reason that destructor does the thing it's doing. If you're 
 sure skipping it is what you want, go ahead.
Biotronic, the code you have provided may be exactly what I am looking for. Let me explain my situation. I have a library that has been ported from a C/C++ code. A struct Bar that is part of the library implements its own refcounted GC. The refcounted GC of course works via constructor and destructor. But this refcounted GC also uses some thread local variables since there is a need to implement parallelism. It all works well so far. But as soon as I create an instance of Bar inside a Dlang class (say Foo) or as part of a Dlang dynamic array, hell follows. At some point, Dlang's GC kicks in and Bar's destructor gets called from within Dlang's GC. Now since Dlang executes GC on a different thread, the destructor gets confused and segfaults. Fortunately for me, it would be fine if the call to destructor is completely avoided within the realm of Dlang's GC since I do not need the Bar instances thereafter. All I need to do is to dismantle Bar completely from the memory to avoid leaks. So long as I can avoid calls to Bar's destructor from within Dlang's GC, I am fine. I also have some limitations. Since we need to run all this on an embedded system with limited RAM (and given that I have millions of instances of Bar which is only 32-bit wide), I do not want to add a flag or other stuff to Bar. And that is the reason we are using thread local storage in the refcounted GC. So there. I will try your way. Let me see if I can make it to work in my situation. Thanks for sharing.
Sep 24
parent reply bitwise <bitwise.pvt gmail.com> writes:
On Monday, 25 September 2017 at 01:46:15 UTC, Haridas wrote:
 [...]

 It all works well so far. But as soon as I create an instance 
 of Bar inside a Dlang class (say Foo) or as part of a Dlang 
 dynamic array, hell follows. At some point, Dlang's GC kicks in 
 and Bar's destructor gets called from within Dlang's GC. Now 
 since Dlang executes GC on a different thread, the destructor 
 gets confused and segfaults.
I actually wrote a C# style Dispatcher for exactly this reason. I have D classes that own non thread-safe resources. So in the destructor of the D class, I add a call that queues the destruction to the main thread's dispatcher. In your case, the postblit of Bar is still going to run and add a ref to it's count when you place it in Foo, right? That means that if you don't destroy it, it will leak memory or resources. Unfortunately, my dispatcher is not production-ready yet, but you can get around this with a simpler approach. Just keep a shared container of your ref counted object type somewhere. When a destructor of a GC class runs, move the ref counted object into the trash container. Then, next time you want to create an instance of the ref counted object, you can empty the trash container at the same time. You should protect the container with a Mutex of some kind. Also, be sure that the container doesn't allocate using the GC since it will be called from class destructors. IIRC std.container.Array uses malloc, not GC, so you may be able to use that.
Sep 24
parent Haridas <haridas systemc.net> writes:
 In your case, the postblit of Bar is still going to run and add 
 a ref to it's count when you place it in Foo, right? That means 
 that if you don't destroy it, it will leak memory or resources.
Actually no. Since when Foo (class that instantiates Bar) gets GCed, that is the point that I need to destroy the whole infrastructure around Bar, including its recounted GC. So I am Ok if destructor of Bar does not get called this one time since I am just going to delete the whole block of memory that I have allocated for placing the refcounted instances of Bar.
Sep 24
prev sibling next sibling parent reply bitwise <bitwise.pvt gmail.com> writes:
On Sunday, 24 September 2017 at 17:11:26 UTC, Haridas wrote:
 In the following code, Bar is an element of struct Foo. Is 
 there a way to avoid a call to ~Bar when ~Foo is getting 
 executed?
Don't construct it to begin with. struct Bar { import std.stdio : writeln; int a = 123; void boink() { writeln(a); } ~this(){ writeln("bar dtor"); } } struct Foo { ubyte[Bar.sizeof] barBuffer; Bar* _bar = null; ref Bar bar() { import std.conv : emplace; if(!_bar) { _bar = cast(Bar*)barBuffer.ptr; emplace(_bar); } return *_bar; } } int main(string[] argv) { Foo foo; foo.bar.boink(); return 0; }
Sep 24
parent reply Adrian Matoga <dlang.spam matoga.info> writes:
On Sunday, 24 September 2017 at 19:52:52 UTC, bitwise wrote:
 On Sunday, 24 September 2017 at 17:11:26 UTC, Haridas wrote:
 In the following code, Bar is an element of struct Foo. Is 
 there a way to avoid a call to ~Bar when ~Foo is getting 
 executed?
Don't construct it to begin with. struct Bar { import std.stdio : writeln; int a = 123; void boink() { writeln(a); } ~this(){ writeln("bar dtor"); } } struct Foo { ubyte[Bar.sizeof] barBuffer; Bar* _bar = null; ref Bar bar() { import std.conv : emplace; if(!_bar) { _bar = cast(Bar*)barBuffer.ptr; emplace(_bar); } return *_bar; } }
You shouldn't store the pointer to barBuffer inside Foo. The language allows moving the structure around with a simple memcpy, so _bar is likely to point into garbage soon after it's assigned. Why don't you just return *cast(Bar*)barBuffer.ptr in bar()? You could still emplace a Bar inside barBuffer in Foo's constructor, if needed.
Sep 25
parent bitwise <bitwise.pvt gmail.com> writes:
On Monday, 25 September 2017 at 08:39:26 UTC, Adrian Matoga wrote:
 [...]

 You shouldn't store the pointer to barBuffer inside Foo. The 
 language allows moving the structure around with a simple 
 memcpy, so _bar is likely to point into garbage soon after it's 
 assigned.
Good point - but it's a mistake ;) 'Foo' is a class in the OP's code, so no problem.
 Why don't you just return *cast(Bar*)barBuffer.ptr in bar()?
Lazy construction
 You could still emplace a Bar inside barBuffer in Foo's 
 constructor, if needed.
So you KNOW it's a class then...since structs can't have default ctors.. :P
Sep 25
prev sibling next sibling parent Elronnd <elronnd em.slashem.me> writes:
Here's a simple solution.  Just make Bar a pointer and free it 
before it can be destructed!


import std.stdio;

struct Bar {
     ~this() {
         writeln("~bar");
     }
}

struct Foo {
     Bar *bar;
     this(int why_the_fuck_dont_structs_have_default_constructors) 
{
         bar = new Bar;
     }
     ~this() {
         writeln("~foo");
         import core.memory;
         GC.free(bar);
     }
}
Sep 27
prev sibling parent Elronnd <elronnd em.slashem.me> writes:
Here's a simple solution.  Just make Bar a pointer and free it 
before it can be destructed!


import std.stdio;

struct Bar {
     ~this() {
         writeln("~bar");
     }
}

struct Foo {
     Bar *bar;
     this(int why_the_fuck_dont_structs_have_default_constructors) 
{
         bar = new Bar;
     }
     ~this() {
         writeln("~foo");
         import core.memory;
         GC.free(bar);
     }
}
Sep 27