Redeclaring data members in extended types

BlitzMax Forums/BlitzMax Programming/Redeclaring data members in extended types

Htbaa(Posted 2008) [#1]
I just found this out when redeclaring data members in a extended type. It's not a tutorial really. But it's good knowledge to have about extending types in BlitzMax.

Given this code:

SuperStrict

Type TBase
	Field a:Int
End Type

Type TExtended Extends TBase
	Field a:Int = 10
End Type

Local obj:TExtended = New TExtended

Print "TExtended.a = " + obj.a
Print "Casted to TBase makes a = " + TBase(obj).a


Results in:
TExtended.a = 10
Casted to TBase makes a = 0


But when reading the code you expect it to be this:
TExtended.a = 10
Casted to TBase makes a = 10


Why does this happen? It seems that when you 'redeclare' a data member that already exists in the base type it becomes a data member that doesn't exist in the base type.

This can lead to some confusing situations.

For example, if in the base type you use the New() method to read the value of data member 'a' to do some calculations or something, it uses the default value of the base type. Which may result in unexpected results.

Even if you don't redeclare the data member, when overloading New() it first executes the New() method of the base type, and then the New() method of the extended type.

I believe in C++ this is done in reversed order. Which is more logical to me.

If you redeclare a data member that has been declared in the base type then when it's casted back to the base type, the value of the redeclared data member will be that of the base type.
If you haven't redeclared the data member and you cast it back to the base type then it'll contain the value of the extended type.


------------------------

Now, if I can open up a little discussion about this subject: Do you think this behavior is wanted? The 'issue' with redeclared data members is something I can understand. But what about the order of how the New() method is being executed? I'm almost sure that with C++ it's reversed. Although of course, I could be wrong.


Azathoth(Posted 2008) [#2]
I thought C++ did it in the same order.


Philip7(Posted 2008) [#3]
'Cast back to the base type'

I sure hope not. It's the same with Methods.
Your extented subtype can override the 'default' of the basetype but can never change the actual base type. Inheritance runs branche-down in the type tree, not up.


Brucey(Posted 2008) [#4]
This can lead to some confusing situations.

Then don't do it?

Why would you want to declare a variable with the same name, twice? Of course it's confusing :-p


Grey Alien(Posted 2008) [#5]
Yeah just don't do it. Methods override and so do fields. Overriding methods is useful for Polymorphism but there's no real use in overriding fields (please correct me if I'm wrong).


ziggy(Posted 2008) [#6]
You're not redeclaring it, you're hideing it. More or less the same as:
Strict
Local Var1:Int = 10
For Local i:int = 0 to 100
   Local Var1:String = "Hello"
   Print Var1
Next
Print Var1

This is a matter of scopes. the variable inside the FOR loop, hides an enclosing variable with the same name. This also applies to fiels on types, as oposite to what happens with methods, where the overriden method substitutes the original extended one.


Htbaa(Posted 2008) [#7]
I see now why that order of execution is maintained that way.

The thing I'm running into is this. In C++ I had created a base class. In the constructor I had a parameter which allowed me to add identifiers (plain integers). This is important since the constructor of the base class would register the object with a entitymanager.

So what I'm trying to achieve is that a data member from the base class will be set in the derived class, so this data is available for use in the constructor of the base class. But I guess this just isn't possible now is it? Like you can in C++ with the constructor of the derived class.


Brucey(Posted 2008) [#8]
Why can't you just do something like :
Type A
  Field x:int
End Type

Type B Extends A

  Function Create:B(x:int)
     Local this:B = New B
     this.x = x
     return this
  End Function
End Type


Isn't that the same thing as
class B : public A
{
public:
   B(int x)
    : x(x)
  {
  }
}



Htbaa(Posted 2008) [#9]
Because in the constructor of the base class I make sure the object gets registered with my entitymanager. Of course, I could call the function to add it to the registry in the constructor of the base class but I don't want to have to make the call to that function manually in every derived class.

Your example doesn't do the same.

In the BlitzMax example the constructor New() is first being run. After that you update the data member 'x'.

In the C++ example you pass the value for data member 'x' to the constructor of the base class. So the value of 'x' is known by the constructor of the base class.


Brucey(Posted 2008) [#10]
Indeed, but you can't pass anything via New, so in effect it does the same job.


Htbaa(Posted 2008) [#11]
True. But a Create function is useless as well for a base class. Because in the derived class you have to stick to the return types and function parameters as stated in the base class. BlitzMax doesn't support overloading functions so that won't be the best approach either.

I suppose I'll have to do it manually then.


TomToad(Posted 2008) [#12]
I find it best not to use New(). I instead prefer a Create() function or method. It is a little more work, but you gain a lot more flexibility that way. For example:

You now can make an instance of any parent or extended type, and pass it parameters. The calling order is from base type to extended type. However, if you move the call to Super.Create() so it is right above the Return Self, then it will be called in extended to base order.
As a matter of fact, by changing where the Super.Create() is called, you can pretty much have the base types called in any order you want at any place in the initialization.

Edit: Just read your reply after I posted. The above method will require the same parameter types be passed to all extended classes, but since I am returning Object and then casting back to the appropriate type at the New command, you can extend this to handle any type.


Htbaa(Posted 2008) [#13]
Thanks for the example! I think this'll work for me. I was unaware of this method: B(New B.Create(10))

It might look a bit tedious/cryptic though but that's fine. I'm a Perl programmer anyway, so I'm used to it.

Thanks.


TomToad(Posted 2008) [#14]
Basically New B.Create(10) creates an instance of B and immediately calls the Create() method. Since the method returns Object, then you need to cast it to B with B(). So that gives you B(New B.Create(10)). You could define the Create() method to return B, so there is no need to cast, but unfortunately, since BlitzMax doesn't allow overloading, you would need to return B for every base type and extended type as well (unless you wanted a CreateA:A(), CreateB:B(), etc...).

It's really just a matter of preference and style. If I don't need to call base class Create()'s, I would just use a Create Function instead of a method like in Brucey's example.


Htbaa(Posted 2008) [#15]
OK, that was 5 minutes work to convert my code :-). It's working like a charm.

I decided to not use any parameters in the Create() method. I can always create some alternative CreateObj function.


Brucey(Posted 2008) [#16]
Yeah, I tend to use the Create method in all my code too, but I usually provide both (function and method) in my modules. Although I'm not sure why you'd return Object. You'd be as well returning the top-most Super class.

:-)


TomToad(Posted 2008) [#17]
Although I'm not sure why you'd return Object. You'd be as well returning the top-most Super class.


You mean use Create:A()? Probably would work just as well in my example. I was just trying to spit out an example of how you can change the initializing order of extended types and Object is a base Type that is common to every one.
Of course, returning Object makes more sense if you could be extending more than one base class. Suppose you have a Type which works with OpenGL and another that works with DirectX and you need to extend whichever one you need, then you couldn't just return TMyOpenGL or TMyOpenDirectX because then they all would have to return the same thing or you will run into a compiler error.

Of course, I could just create a base TGraphicsDriver class, abstract everything that is needed to be implemented by TMyDirectX and TMyOpenGL, then return TGraphicsDriver. Not sure though, how adding an extra abstracted layer would affect performance (I would imagine that performance wouldn't be affected at all).


Grey Alien(Posted 2008) [#18]
I use Create() but I return the actual type not the Object for what it's worth.