www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - D structs weak identity and RAII

reply Boris-Barboris <ismailsiege gmail.com> writes:
Hello, I was trying to write some templated unique pointers.
Idea was simple: struct UniquePointer that handles underlying 
pointer in RAII-style and WeakPointer struct that is spawned by 
UniquePointer. Weak pointer is handled differently in my 
collections, wich subscribe to the event of UniquePointer 
destruction, so that there are no dangling references left all 
over the heap (I use Mallocator). Collection's insertFront\Back 
methods register callbacks in Weak pointer itself, and all 
existing weak pointers are registered in Unique pointer.

That, unfortunately, failed at the very beginning. Then I wrote a 
unit test to investigate:

https://dpaste.dzfl.pl/d77c72198095

1). line 47 and 76 together mean, that there is basically no 
reliable way to write uniqueptr method wich returns WeakPointer, 
registered by the correct pointer in it's constructor. Or is 
there? Is usage of &this in constructor (or in struct methods in 
general) fundamentally unreliable in D?
2). postblit was never called when returning struct from 
function. I noticed it was called once on line 91, if i removed 
opAssign with ref argument. What are the rules? Why is it always 
called when passing struct to function and creating local copy of 
the parameter (lines 113, 127, 139), and why is opAssign not 
called?
3). Is there a way to reliably encapsulate all assignments? Looks 
like complete mess will happen should I apply some std.algorithm 
functions on array of structs with overloaded operators.
4). Any suggested workarounds? I never tried it in C++, but, 
IIRC, struct life cycle is much more consistent there ang gives 
the strict control I desire.
5). If all this is a design choice, what is the reason behind it?
Jun 18 2017
parent reply =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
On 06/18/2017 06:22 PM, Boris-Barboris wrote:

 https://dpaste.dzfl.pl/d77c72198095

 1). line 47 and 76 together mean, that there is basically no reliable
 way to write uniqueptr method wich returns WeakPointer, registered by
 the correct pointer in it's constructor. Or is there? Is usage of &this
 in constructor (or in struct methods in general) fundamentally
 unreliable in D?
It's unreliable because structs are value types in D, which means that they can be moved around freely. This is why self-referencing structs are illegal in D.
 2). postblit was never called when returning struct from function.
Rvalues are automatically moved and there is also RVO.
 3). Is there a way to reliably encapsulate all assignments?
I think so.
 4). Any suggested workarounds? I never tried it in C++, but, IIRC,
 struct life cycle is much more consistent there ang gives the strict
 control I desire.
Yes, C++ has a very well defined object lifecycle.
 5). If all this is a design choice, what is the reason behind it?
Some are design choices and some are bugs. For example, a fundamental bug has just been fixed: When a constructor threw, the destructors of already-constructed members were not being called. I think the fix is in git head but not released yet. I can't claim expertise but here is a quick and dirty proof of concept that Vittorio Romeo and I had played with a few weeks ago: import core.stdc.stdio; import core.stdc.stdlib; import std.algorithm; import std.stdio; shared byte b; // to prevent compiler optimizations struct UniquePtr { void * p; this(void * p) { this.p = p; import core.atomic; core.atomic.atomicOp!"+="(b, 1); } ~this() { free(p); } disable this(this); UniquePtr move() { void * old = p; p = null; return UniquePtr(old); } } void consumer(UniquePtr u) { } UniquePtr producer(int i) { auto u = UniquePtr(malloc(42)); return i ? UniquePtr(malloc(56)) : u.move(); } void main() { consumer(UniquePtr(malloc(2))); auto u = UniquePtr(malloc(5)); consumer(u.move()); auto u2 = producer(43); } Note how post-blit is disabled there. In addition to a moveFrom(), that's exactly what Atila Neves had to do in this automem library as well: https://github.com/atilaneves/automem Ali
Jun 18 2017
parent Boris-Barboris <ismailsiege gmail.com> writes:
On Monday, 19 June 2017 at 06:34:49 UTC, Ali Çehreli wrote:
 It's unreliable because structs are value types in D, which 
 means that they can be moved around freely. This is why 
 self-referencing structs are illegal in D.
I guess it's more like the spec states, that they can be moved vithout notice. Value type semantics themselves do not mean spontaneous uncontrolled mobility.
 I can't claim expertise but here is a quick and dirty proof of 
 concept that Vittorio Romeo and I had played with a few weeks 
 ago:

 ...

 shared byte b; // to prevent compiler optimizations

 struct UniquePtr {
     void * p;

     this(void * p) {
         this.p = p;
         import core.atomic;
         core.atomic.atomicOp!"+="(b, 1);
     }
Very interesting hack, thank you.
 Note how post-blit is disabled there. In addition to a 
 moveFrom(), that's exactly what Atila Neves had to do in this 
 automem library as well:

   https://github.com/atilaneves/automem
Nice, thanks.
Jun 19 2017