Confusion regarding struct lifecycle

Ali Çehreli via Digitalmars-d-learn digitalmars-d-learn at puremagic.com
Tue Feb 16 18:23:52 PST 2016


On 02/16/2016 05:50 PM, Matt Elkins wrote:
> On Tuesday, 16 February 2016 at 08:18:51 UTC, Ali Çehreli wrote:
>> When a temporary Foo object is moved into the array, the temporary
>> object is set to Foo.init. This temporary object lives on the stack.
>> In fact, all temporary Foo objects of Foo.this(int) live at the same
>> location.
>>
>> After Foo(8) is moved into the array and set to Foo.init, now Foo(1)
>> is constructed on top of it. For that to happen, first the destructor
>> is executed for the first life of the temporary, and so on...
>>
>> There is one less Foo.init destruction because conceptually the
>> initial temporary was not constructed on top of an existing Foo.init
>> but raw memory.
>
> I guess that makes sense. But doesn't it imply that a copy is happening,
> despite the @disabled post-blit? The desired behavior was to construct
> the Foos in place, like with a C++ initializer list.
>

I changed my mind! :) This seems to be how D is being clever in 
constructors. I've just learned something (thank you!), which may very 
well be a bug rather than a feature.

The compiler analyzes the body of the constructor to differentiate 
between first use versus first initialization. For example, unlike C++, 
you can call super() anywhere in the constructor.

So, although this(this) is disabled, moving objects into a static-array 
member in a constructor must be allowed because otherwise we could not 
initialize such members. Since a static array must consist of .init 
values to begin with, every move into its members must also trigger its 
destructor if the type has elaborate destructor.

In this example, the compiler is being smart and skips one of the 
"move+destructor" operations. I felt this behavior in my other post 
where I had alluded to "raw memory".

This is what I've discovered: Try printing the static array at the 
beginning of the constructor:

     this(int)
     {
         writefln("Initial foos:");
         foreach (ref f; foos[]) {
             writeln(" ", f.value);
         }
         writeln("starting to assign");

         // ...
     }

Error:  field 'foos' initialization is not allowed in loops or after labels

So, this tells us (in a cryptic way) that the syntax foos[] at that 
point in the constructor is understood as "initialization" not as 
slicing. To contrast, now move that code below the Foo(8) assignment:


struct Foo
{
     // ...
     int value = 7;    // <-- Do this as well
}

     this(int)
     {
         writeln("Before 8");
         foos[0] = Foo(8);         // <-- FIRST ASSIGNMENT

         writefln("Initial foos:");
         foreach (ref f; foos[]) {
             writeln(" ", f.value);
         }
         writeln("starting to assign");

         // ...
     }

Now the code compiles and prints the contents of the array *without* any 
Foo destructor. (I think this is because Foo(8) is emplaced, rather than 
assigned.)

Before 8
Initial foos:
  8
  7
  7
  7
  7
starting to assign
Before 1
[...]

Only after that point we have an array of valid Foos where further 
assignments trigger destructors.

Ali



More information about the Digitalmars-d-learn mailing list