I want create abstract class with serializable childs. But child deserialization not work.
Why this test failed?
class SerialTest : Test{
Void testChange(){
c := Child()
c.i = 1
sb := StrBuf()
sb.out.writeObj(c)
verifyEq(1, sb.toStr.in.readObj->i)
}
}
@Serializable
abstract class Parent{
new make(|This|? f:=null){
f?.call(this)
}
}
class Child : Parent{
Int i := 0
new make(|This|? f:=null):super.make(f){ }
}
Output:
TEST FAILED
sys::TestErr: Test failed: 1 [sys::Int] != 0 [sys::Int]
brianMon 18 Jun 2012
I think the problem there is that your field initializer for Child.i runs after Parent.make calls your function. So you need to move the it-block function call into Child.
AkcelistoMon 18 Jun 2012
I suppose that field initializers runs before constructor call:
class C{
Int i := 0
new make(){
i = 1
}
static Void main(){
echo(c.i)
}
}
Prints:
1
But if I write that:
class A{
new make(){
trap("i", [1])
}
}
class B:A{
Int i := 0
new make(){ // why parent ctor not called?
}
}
class SerialTest : Test{
Void testBefore(){
verifyEq(1, B()->i)
}
}
Output:
TEST FAILED
sys::TestErr: Test failed: 1 [sys::Int] != 0 [sys::Int]
What is sequence of calling ctors and field initializers?
KevinKelleyMon 18 Jun 2012
Good one. There's some consolidation of initializers that happens, but I'm not sure I can explain it.
The full object is created, zero-initialized, before you can see it.
constructor sequence is next, base to derived. In each class, field initializers are consolidated into a <init> block; each such block runs before any constructor for that class.
the compiler enforces that the this/super constructor-chaining happens in correct order. You can chain within a class however you like, but the chain has to follow the super-first rule.
with blocks throw some confusion into the mix, because they happen (usually) during the base-class constructor execution, before the subclass initializers have run.
It's certainly possible I've screwed this up; it still confuses me. I don't even remember now what happens with virtual method calls in a constructor, please don't go there next. :-)
In your trap("i", [1]) above, then, adding an echo(this->i) shows that it is setting the derived member, but the next step is to enter the derived constructor chain, overwriting it.
That's my story, anyway.
AkcelistoTue 19 Jun 2012
I think, current creation sequence is:
obj creation
super fields initializer
super ctor
this fields initializer
this ctor
Better if creation sequence will be:
obj creation
super fields initializer
this fields initializer
super ctor
this ctor
brianWed 20 Jun 2012
Current sequence is correct. As a general principle you really want to fully initialize the base class before initializing a subclass. This is how it works in Java too. The complexity of constructors is one of many reasons I really dislike inheritance - I hardly use inheritance in my code at all these days.
Akcelisto Mon 18 Jun 2012
I want create abstract class with serializable childs. But child deserialization not work.
Why this test failed?
Output:
brian Mon 18 Jun 2012
I think the problem there is that your field initializer for Child.i runs after Parent.make calls your function. So you need to move the it-block function call into Child.
Akcelisto Mon 18 Jun 2012
I suppose that field initializers runs before constructor call:
Prints:
But if I write that:
Output:
What is sequence of calling ctors and field initializers?
KevinKelley Mon 18 Jun 2012
Good one. There's some consolidation of initializers that happens, but I'm not sure I can explain it.
this/super
constructor-chaining happens in correct order. You can chain within a class however you like, but the chain has to follow the super-first rule.with
blocks throw some confusion into the mix, because they happen (usually) during the base-class constructor execution, before the subclass initializers have run.It's certainly possible I've screwed this up; it still confuses me. I don't even remember now what happens with virtual method calls in a constructor, please don't go there next. :-)
In your
trap("i", [1])
above, then, adding anecho(this->i)
shows that it is setting the derived member, but the next step is to enter the derived constructor chain, overwriting it.That's my story, anyway.
Akcelisto Tue 19 Jun 2012
I think, current creation sequence is:
super
fields initializersuper
ctorthis
fields initializerthis
ctorBetter if creation sequence will be:
super
fields initializerthis
fields initializersuper
ctorthis
ctorbrian Wed 20 Jun 2012
Current sequence is correct. As a general principle you really want to fully initialize the base class before initializing a subclass. This is how it works in Java too. The complexity of constructors is one of many reasons I really dislike inheritance - I hardly use inheritance in my code at all these days.