www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - initialization lists ?

reply Daniel919 <Daniel919 web.de> writes:
Hi, in a c++ faq, it's said that they are more efficient than using 
assignment in ctor, because they avoid the need to create/delete a 
temporary object, that contains the actual value.

So, according to the article 
http://www.parashift.com/c++-faq-lite/ctors.html#faq-10.6,
this is prefered...

   class Foo {
     int x;
     Foo(int i) : x(i) {};
   };

...over

   class Foo {
     int x;
     Foo(int i) { x = i; };
   };


In the former case, when the ctor "creates" the class, it directly
assigns the value of i to x, as if someone had declared it like:
   class Foo {
     int x = i; //Impossible of course
     Foo(int i) : x(i) {};
   };

In the latter case "i" is the temporary object, that only
exists to get its value assigned to x.


How does D handle this ? There are no initialization lists.

But I wonder, are they necessary at all ?
Couldn't the optimizer just look over the complete ctor, recognize this 
simple assignments and automatically assign the values, the way it's 
done with initialization lists in c++ and so avoid the creation of 
temporary objects ?


Daniel
Jul 04 2007
parent reply "Jarrett Billingsley" <kb3ctd2 yahoo.com> writes:
"Daniel919" <Daniel919 web.de> wrote in message 
news:f6hbud$1iga$1 digitalmars.com...
 So, according to the article 
 http://www.parashift.com/c++-faq-lite/ctors.html#faq-10.6,
 this is prefered...

   class Foo {
     int x;
     Foo(int i) : x(i) {};
   };

 ...over

   class Foo {
     int x;
     Foo(int i) { x = i; };
   };


 In the former case, when the ctor "creates" the class, it directly
 assigns the value of i to x, as if someone had declared it like:
   class Foo {
     int x = i; //Impossible of course
     Foo(int i) : x(i) {};
   };

 In the latter case "i" is the temporary object, that only
 exists to get its value assigned to x.


 How does D handle this ? There are no initialization lists.

One, your example is a little wrong: that FAQ also mentions that "There is no performance difference if the type of x_ is some built-in/intrinsic type, such as int." So you should be using a class type instead. Two, D doesn't allow for the same kind of object semantics that C++ does. C++ allows classes to have their memory allocated inside other classes, which is why using initialization lists are faster: rather than creating that temp object and copying it into the memory inside 'this', it just runs the constructor for the internal object on the memory already inside 'this'. D only allows classes to be allocated on the heap (well, they can be allocated on the stack in some circumstances, but not in the general case like in C++). Therefore, if one of your class's members is another class that needs to be constructed, it's going to have to be new'ed anyway, so there's no way to optimize it. D does, however, have structs which can have their memory allocated inside other aggregates (structs, classes, unions). In this case, when you use the "fake" struct ctor (a static S opCall), the compiler can optimize the call to this function and it becomes as fast as a simple assignment to the struct's members. (Structs are rumored to be getting ctors soon, so hopefully it will be less arcane and "magical"-looking.)
Jul 04 2007
parent reply Daniel919 <Daniel919 web.de> writes:
 One, your example is a little wrong: that FAQ also mentions that "There is 
 no performance difference if the type of x_ is some built-in/intrinsic type, 
 such as int."  So you should be using a class type instead.

So I guess that's what I wondered about: The optimizer can turn these simple assignments into sth like a initialization list.
 Two, D doesn't allow for the same kind of object semantics that C++ does. 
 C++ allows classes to have their memory allocated inside other classes, 

Classes in D are always stored by ref. that's the point, right ? MyClass foo = new MyClass(); <- this allocates an instance of MyClass somewhere on the heap and puts a ref to it into foo. So sizeof(foo) is always constant.
 which is why using initialization lists are faster: rather than creating 
 that temp object and copying it into the memory inside 'this', it just runs 
 the constructor for the internal object on the memory already inside 'this'.

class Foo { AnotherClass v; Foo(AnotherClass t) { v = t; }; }; Foo f = new Foo(a); The assignment way: While creating the instance of Foo for f... 1. The internal object v gets created inside the instance of f. 2. The temp instance t gets created somewhere outside the actual class instance of f. 3. a is copied into t. 4. t is copied into v. Using initialization lists: While creating the instance of Foo for f... 1. The internal object v gets created inside the instance of f. 2. a is copied into v.
 D only allows classes to be allocated on the heap (well, they can be 
 allocated on the stack in some circumstances, but not in the general case 
 like in C++).  Therefore, if one of your class's members is another class 
 that needs to be constructed, it's going to have to be new'ed anyway, so 
 there's no way to optimize it.

Because (in the example above) the instance of v can be located somewhere on the heap (v only holds the reference to it), so it doesn't have to be within the instance of f. And the copy of reference can be treated like c++ optimizers do for built-in/intrinsic types. a (reference to an instance of Foo) is copied into v directly, without the need for a temporary object, holding the passed-in ref. Or even if it isn't optimized away: Copying of refs is trivial compared to copying of the class's data.
 D does, however, have structs which can have their memory allocated inside 
 other aggregates (structs, classes, unions).  In this case, when you use the 
 "fake" struct ctor (a static S opCall), the compiler can optimize the call 
 to this function and it becomes as fast as a simple assignment to the 
 struct's members.  (Structs are rumored to be getting ctors soon, so 
 hopefully it will be less arcane and "magical"-looking.) 

I wonder: Then this would be the only case where initialization lists could come into play for D ? struct MyStruct { MyStruct parent; //error, discussed below int id; int value; this(MyStruct t, int i, int j) { v = t; id = i; value = j; } } But no wait, "MyStruct v;" this is not working, because it's an unresolveable forward reference. Makes sense, if you try to figure out how much bits to allocate for a single instance of MyStruct: id +4, value +4, but hmm... how much for v ? (goto beginning of line) It's an infinite recursion. So one has to use a ptr instead (which always has a const size, independed of MyStruct): struct MyStruct { MyStruct* v; ... } And then it's an equal situation as for classes (and their refs): In the ctor the simple assignment of the ptr can be optimized away (like c++ compilers do for built-in/intrinsic types). Or even if it isn't optimized away: Copying of ptrs is trivial compared to copying of the struct's data. Did I get things right ? Otherwise please correct me. Daniel
Jul 05 2007
parent reply "Jarrett Billingsley" <kb3ctd2 yahoo.com> writes:
"Daniel919" <Daniel919 web.de> wrote in message 
news:f6ig0v$u6k$1 digitalmars.com...
 Classes in D are always stored by ref. that's the point, right ?
 MyClass foo = new MyClass(); <- this allocates an instance of MyClass 
 somewhere on the heap and puts a ref to it into foo. So sizeof(foo) is 
 always constant.

Right.
   class Foo {
     AnotherClass v;
     Foo(AnotherClass t) { v = t; };
   };
   Foo f = new Foo(a);

 The assignment way:
 While creating the instance of Foo for f...
 1. The internal object v gets created inside the instance of f.
 2. The temp instance t gets created somewhere outside the actual class 
 instance of f.
 3. a is copied into t.
 4. t is copied into v.

And as you explain below, since class variables are always references, the copying of a to t to v only copies references, a much faster operation than copying the entire class.
 Because (in the example above) the instance of v can be located somewhere 
 on the heap (v only holds the reference to it), so it doesn't have to be 
 within the instance of f.
 And the copy of reference can be treated like c++ optimizers do for 
 built-in/intrinsic types. a (reference to an instance of Foo) is copied 
 into v directly, without the need for a temporary object, holding the 
 passed-in ref.
 Or even if it isn't optimized away: Copying of refs is trivial compared to 
 copying of the class's data.

Right.
 Did I get things right ? Otherwise please correct me.

Yep. Something else to keep in mind is that D doesn't really allow for uninitialized members. When you declare a class, the compiler generates a "static" instance of it with all the members initialized to their defaults (either one that you specify with i.e. "int i = 5;" or with the type's default). When you instantiate a class or struct, it will allocate the memory and then copy the static instance into that block, so that there are no uninitialized members. It would be pretty difficult (but not impossible) for the compiler to do initialization list optimizations, because it'd have to alternate between copying out of the static initializer and copying from the stack. It might even trash the cache, ending up with about the same performance as now. Basically the only time large amounts of data are passed on the stack (and where something like initialization lists would be useful) are when passing structs by value, and hopefully you're not passing too many big ones around.
Jul 05 2007
parent Daniel919 <Daniel919 web.de> writes:
 Something else to keep in mind is that D doesn't really allow for 
 uninitialized members.  When you declare a class, the compiler generates a 
 "static" instance of it with all the members initialized to their defaults 
 (either one that you specify with i.e. "int i = 5;" or with the type's 
 default).  When you instantiate a class or struct, it will allocate the 
 memory and then copy the static instance into that block, so that there are 
 no uninitialized members.

This shows exactly what you explained: ---------------------------------------------------------------------------- import std.stdio; class SubClass { static this() { writefln("static SubClass created"); } this() { writefln("SubClass created"); } static ~this() { writefln("static SubClass destroyed"); } ~this() { writefln("SubClass destroyed"); } } class MyClass { SubClass subclass; this() { writefln("MyClass created"); } static this() { writefln("static MyClass created"); } ~this() { writefln("MyClass destroyed"); } static ~this() { writefln("static MyClass destroyed"); } } void main() { writefln("----"); MyClass myclass1 = new MyClass(); delete myclass1; writefln("----"); } ---------------------------------------------------------------------------- static SubClass created static MyClass created ---- MyClass created MyClass destroyed ---- static MyClass destroyed static SubClass destroyed
 It would be pretty difficult (but not impossible) 
 for the compiler to do initialization list optimizations, because it'd have 
 to alternate between copying out of the static initializer and copying from 
 the stack.  It might even trash the cache, ending up with about the same 
 performance as now.

Yes, it would have to make sure that the temporary object is not used except for getting its value assigned to the according member object of the class. Thanks for your replies, Jarrett ! Daniel
Jul 05 2007