On Wednesday, 28 May 2014 at 16:25:40 UTC, Rene Zwanenburg wrote:
On Wednesday, 28 May 2014 at 09:37:55 UTC, Edwin van Leeuwen wrote:
Thank you for the reply. Does this mean I should never initialize classes/objects like that or is it more specific to RBT?

It's the same for any class.

I guess structs have the same problem with classes/objects?

That's right.

That makes struct that hold objects quite awkward to use, since you can't define a default constructor for structs.

struct S
{
   auto tree = new RedBlackTree!string();
}

There is a workaround for struct default construction. First let me describe the way D initialization works:

Every type, built in or user defined, has an init value. For ints this is 0, floats NaN, and so on. User defined types have their init value stored in the corresponding TypeInfo, for example [0], [1].

When initializing a value type the space is already reserved, either on the stack or on the heap inside a dynamic array. All the runtime does is memcpy the init value of that type to the right location.

When new-ing a class the GC first allocates some space on the heap. Then it copies the init value to that location, just like with a value type. Finally it calls a constructor on the newly allocated object.

When declaring a field in your struct or class and assign a default value to it, it ends up in the init value for your type. For example:

class C
{
  int i = uniform(0, 10); // Uniform random number
}

The generated random value will end up in C's init value and thus be the same for every instance. Generating the number in a constructor will of course result in a fresh random number for every instance.

The reason initialization works this way is, as I understand it, both safety and speed. The memcpy will never fail, so if you successfully allocated the space for something it will _always_ be properly initialized. And since the init value of everything is known at compile time the init values of composite types contain the init values of all contained fields. In other words, no matter how complex your type, it will always be initialized with a single memcpy.

Now, a workaround for struct default constructors is to use static opCall:

struct S
{
  int i;

@disable this(); // Disable default initialization. See the comment in [1]

  private this(int i)
  {
    this.i = i;
  }

  static auto opCall()
  {
    return S(uniform(0, 10));
  }
}

void main()
{
  S s; // Should not compile. No init value for S
  auto s = S(); // Calls S.opCall
}

I hope my ramblings make it a bit more clear what is happening instead of confusing the hell out of you ;). I didn't try any of this code so there may be some typos.


[0]https://github.com/D-Programming-Language/druntime/blob/master/src/object_.d#L844
[1] https://github.com/D-Programming-Language/druntime/blob/master/src/object_.d#L1060

Thank you very much for the clear description! That cleared things up a lot.

Reply via email to