#1917 Serialization quiz

Akcelisto Mon 18 Jun 2012

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]

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:

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?

KevinKelley Mon 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.

Akcelisto Tue 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

brian 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.

Login or Signup to reply.