On Wed, 21 Oct 2009 20:15:16 +0400, Andrei Alexandrescu
<[email protected]> wrote:
Today, structs can't write their own this(). There aren't very solid
reasons for that except that it makes language implementation more
difficult.
I wonder how much of a problem that could be in practice. I realized
today that the "Counted" example - a classic C++ primer example
featuring a struct that counts its own instances - cannot be implemented
in D.
In C++ the counted example looks like this:
struct Counted {
static unsigned count;
unsigned myCount;
Counted() { myCount = count++; }
Counted(const Counted& rhs) { myCount = count++; }
Counted& operator=(const Counted& rhs) {
// no writing to myCount
return *this;
}
~Counted() {
--count;
}
}
In D there's no chance to write Counted because you can always create
Counted objects without executing any code.
struct Counted {
static uint count;
uint myCount;
this() { myCount = count++; } // ERROR
this(this) { myCount = count++; }
ref Counted opAssign(Counted rhs) {
// no writing to myCount
return this;
}
~this() {
--count;
}
}
This being a toy example, I wonder whether there are much more serious
examples that would be impossible to implement within D.
Andrei
I agree it's a very annoying limitation, but I believe there is a
rationale behind it.
Imagine a class that aggregates a struct:
struct Foo { ... }
class Bar
{
Foo foo;
this() {}
// ...
}
The class object construction is now consists of two steps:
1) memcpy the Bar.classinfo.init
2) call __ctor()
Unlike C++, there is no stage at which class members are being
initialized, and it simplifies things quite a lot.
Think about the following: what happens if structs will be allowed default
ctors? How to avoid double initialization?
You may end up with design very similar to C++ in this case (including an
initialization list).
Some problems could be solved with an enforced explicit initialization of
each member. That's the only way I see now that would avoid double
initialization of a struct in presence of default and non-default ctors:
struct Scoped(T) // I like this name a lot more than InPlace, and
especially InSitu
{
this(Args...)(Args args)
{
T obj = cast(T)data.ptr;
obj.__ctor(args);
}
ubyte[T.classinfo.init.length] data = T.classinfo.init; // I believe
this should work at compile-time, since classinfo is immutable
}
class Foo
{
this() {}
this(int i) {}
}
class Bar
{
Scoped!(Foo) foo;
this()
{
// foo is already constructed by now (default ctor is called)
which is cool
// but what if I need to initialize it with some other ctor?
foo = Scoped!(Foo)(42); // double initialization
}
int doesntNeedToBeInitializedExplicitlyInACtor = 17;
}
Enforcing explicit initialization of each member (unless its value is set
at the declaration, as in the example above) is gracefully solving this
issue.
Also think about exception safety: what if an exception is thrown in a
class ctor - should the dtors be invoked on initialized members or not? If
yes, in what order? D doesn't enforce initialization order, so it's a bit
tricky to determine what members are already initialized and need to be
destroyed (with a dtor call) efficiently. C++ handles issues like this
very well IMO. I believe D should also have a sound solution to this
problem.