New fbc branch ('inheritance') on sourceforge (fbc SVN)
Are there plans to complete the user-define type support by adding the ability to define static member data (in addition to the static member function)?
Behavior:
- When a member data is declared as static, only one copy of the data is maintained for all instances of the type.
- Static member data can be accessed without using an instance of the corresponding type or from any instance of this type.
Example of proposed future use:
My method presently to simulate static member data:
Behavior:
- When a member data is declared as static, only one copy of the data is maintained for all instances of the type.
- Static member data can be accessed without using an instance of the corresponding type or from any instance of this type.
Example of proposed future use:
Code: Select all
Type animal
Declare Constructor ()
' ..........
Dim weight As Single
' ..........
Static count As Integer
End Type
Constructor animal ()
' ..........
animal.count += 1
End Constructor
Print animal.count
Dim dog As animal
Dim cat As animal
Print animal.count, dog.count, cat.count ' the three forms are valid
Sleep
Code: Select all
Namespace animal
Type kind
Declare Constructor ()
Declare Property count () As Integer
Declare Property count (Byval value As Integer)
' ..........
Dim weight As Single
' ..........
End Type
Dim count As Integer
Constructor kind ()
' ..........
animal.count += 1
End Constructor
Property kind.count() As Integer
Return animal.count
End Property
Property kind.count(Byval value As Integer)
animal.count = value
End Property
' ..........
End Namespace
Print animal.count
Dim dog As animal.kind
Dim cat As animal.kind
Print animal.count, dog.count, cat.count ' the three forms are valid
Sleep
-
- Posts: 8586
- Joined: May 28, 2005 3:28
- Contact:
hello fxm
as a workaround i simulate an instance shared counter with a static function
http://www.freebasic.net/forum/viewtopic.php?t=17558
Joshy
as a workaround i simulate an instance shared counter with a static function
http://www.freebasic.net/forum/viewtopic.php?t=17558
Joshy
Yes.D.J.Peters wrote:hello fxm
as a workaround i simulate an instance shared counter with a static function
http://www.freebasic.net/forum/viewtopic.php?t=17558
Joshy
Before I have also used this principle to simulate static data within Type ... End Type:
http://www.freebasic.net/forum/viewtopi ... 199#129199
In my last simulation, I use another method with Namespace ... End Namespace to encapsulate a global data, and with property to allow access from any instance.
My specific example above shows its use to manage a counter of objects, but its use may be more general:
My last method simulates a static member data with full access in read and write from namespace-identifier or from any instance of namespace-identifier.type-name.
- Other example to simulate static member data with full access from namespace-identifier (animal) or from any instance of namespace-identifier.type-name (dog, cat):
Code: Select all
Namespace animal
Type kind
' ..........
Dim weight As Single
Declare Property list () As String
Declare Property list (Byref newlist As String)
' ..........
End Type
Dim list As String
Property kind.list() As String
Return animal.list
End Property
Property kind.list(Byref newlist As String)
animal.list = newlist
End Property
' ..........
End Namespace
animal.list = "animals : "
Print animal.list
Dim dog As animal.kind
dog.list = dog.list + "dog "
Dim cat As animal.kind
cat.list = cat.list + "cat "
Print animal.list, dog.list, cat.list ' the three forms are valid
Sleep
Code: Select all
Namespace animal
Type kind
' ..........
Dim weight As Single
' ..........
End Type
Dim list As String
' ..........
End Namespace
animal.list = "animals : "
Print animal.list
Dim dog As animal.kind
animal.list = animal.list + "dog "
Dim cat As animal.kind
animal.list = animal.list + "cat "
Print animal.list
Sleep
In order to simplify the coding of this same example, 2 macros with parameters may be used:fxm wrote:In my last simulation, I use another method with Namespace ... End Namespace to encapsulate a global data, and with property to allow access from any instance.
My last method simulates a static member data with full access in read and write from namespace-identifier or from any instance of namespace-identifier.type-name.
- Other example to simulate static member data with full access from namespace-identifier (animal) or from any instance of namespace-identifier.type-name (dog, cat)
- one for the static data property declaration block (called for this example with the 2 parameters list, STRING),
- another for the static data body block (called for this example with the 4 parameters animal, kind, list, STRING)
Code: Select all
#MACRO _STATIC_DATA_DECLARATION(_data_name, _data_type) ' to be inserted in the type block (_type_name) inside the namespace block (_namespace_name)
DECLARE PROPERTY _data_name () AS _data_type
DECLARE PROPERTY _data_name(BYREF AS _data_type)
#ENDMACRO
#MACRO _STATIC_DATA_BODY(_namespace_name, _type_name, _data_name, _data_type) ' to be inserted below the type block (_type_name) inside the namespace block (_namespace_name)
DIM _data_name AS _data_type
PROPERTY _type_name._data_name() AS _data_type
RETURN _namespace_name._data_name
END PROPERTY
PROPERTY _type_name._data_name(BYREF _new_data AS _data_type)
_namespace_name._data_name = _new_data
END PROPERTY
#ENDMACRO
NAMESPACE animal
TYPE kind
' ..........
DIM weight AS SINGLE
' ..........
_STATIC_DATA_DECLARATION(list, STRING)
END TYPE
_STATIC_DATA_BODY(animal, kind, list, STRING)
' ..........
END NAMESPACE
animal.list = "animals : "
PRINT animal.list
DIM dog AS animal.kind
dog.list = dog.list + "dog "
DIM cat AS animal.kind
cat.list = cat.list + "cat "
PRINT animal.list, dog.list, cat.list ' the three forms are valid
SLEEP
Static data members are in the to do list but won't be added atm - at least i can't work on that, sorry.
I added the IS operator, now you can do (using sir_mud's example):
No virtual methods yet. They may be added much faster now that support for RTTI was implemented.
I added the IS operator, now you can do (using sir_mud's example):
Code: Select all
const DOG_ACTION = "rub belly"
const CAT_ACTION = "sleep"
type Animal extends Object '' must extend Object so RTTI will be generated for this class
dim as string name
end type
type Dog extends Animal
declare constructor (name_ as string)
declare function getAction as string
end type
constructor Dog (name_ as string)
base.name = name_
end constructor
function Dog.getAction as string
return DOG_ACTION
end function
type Cat extends Animal
declare constructor (name_ as string)
declare function getAction as string
end type
constructor Cat (name_ as string)
base.name = name_
end constructor
function Cat.getAction as string
return CAT_ACTION
end function
function getAction( a as Animal ) as string
if( a is Dog ) then
return cast( Dog ptr, @a )->getAction
elseif( a is Cat ) then
return cast( Cat ptr, @a )->getAction
end if
end function
sub main
var pluto = Dog( "Pluto" )
var tom = Cat( "Tom" )
assert( getAction( pluto ) = DOG_ACTION )
assert( getAction( tom ) = CAT_ACTION )
print "all tests ok"
end sub
main
-
- Posts: 8586
- Joined: May 28, 2005 3:28
- Contact:
That "is" keyword sounds a bit funky (if useful) to me...
I'm a little worried about an operator that checks for what the type of something is. You're not supposed to be able to know what type something is in a statically-typed language.. it adds forced bloat. Then again I could be totally be wrong and the type info could be inevitable in inheritance...
Is the "is" somehow implemented as a compile-time macro? Or...
I'm a little worried about an operator that checks for what the type of something is. You're not supposed to be able to know what type something is in a statically-typed language.. it adds forced bloat. Then again I could be totally be wrong and the type info could be inevitable in inheritance...
Is the "is" somehow implemented as a compile-time macro? Or...
IS and AS are standard Delphi operators. AS is usually a safe cast (exceptions is thrown when the cast fails), I don't know how other languages do this, I assume with some variantion on CAST() builtin instead of an operator.agamemnus wrote:That "is" keyword sounds a bit funky (if useful) to me...
That's what inheritance does. In a certain type (TAncestor), in it there can be TAncestor, but also one of the descendants. IS allows to make a difference.I'm a little worried about an operator that checks for what the type of something is. You're not supposed to be able to know what type something is in a statically-typed language.
The main use for IS is if you have a base class say two main inheritance tree. (say TOrganism with TPlant and TAnimal as descendant) , that you can make a difference in some general routine that takes an TOrganism. E.g. to set an extra field or so.
This gets even more important when meta classes are supported. (variables that can hold a classtype).
Hardly, since it is a few bytes per class definition. Not per instance. Some of the information is also used for generalized streaming (it is more or less the identification of a class)it adds forced bloat.
Usually it is a function, since you must walk the inheritance chain with a loop. (you start with the current class, and then walk till you find either the root object or the class that you are checking for)Then again I could be totally be wrong and the type info could be inevitable in inheritance...
Is the "is" somehow implemented as a compile-time macro? Or...
"x aS TY" is roughly equal to
Code: Select all
if x is ty then
result:=ty(x) // normal cast
else
raise EInvalidCastException;
I realize the usefulness of "IS"...
Yes, it's a few bytes per class definition, but also per instance... In order for the program to know what type a variable is, it needs to store that information, right? If you have 2^16 types max, that's 2 bytes per type instance. If you have a lot of instances and little data in each instance (like, 3 bytes), that is very significant.marcov wrote: Hardly, since it is a few bytes per class definition. Not per instance. Some of the information is also used for generalized streaming (it is more or less the identification of a class)
An inheritance chain makes sense if you could change the inheritance of a UDT on the fly... but, the inheritance scheme is set in stone when you compile. If it was a function, I'd imagine that it would/should use a binary lookup table that is created upon compilation, since that would always theoretically be faster than an inheritance chain.Usually it is a function, since you must walk the inheritance chain with a loop. (you start with the current class, and then walk till you find either the root object or the class that you are checking for)
When doing inheritance, any instance has one pointer back to the classtype. Regardless of IS.agamemnus wrote:
Yes, it's a few bytes per class definition, but also per instance... In order for the program to know what type a variable is, it needs to store that information, right?
This because if you want VMTs (virtual method table, the struct that virtual methods operate on) a pointer from instance back to the type can't be escaped. Any OO language has this.
That's why C++ objects are either auto-initialized or initialized dynamically via NEW. But they are always initialized. Why? That VMT pointer must be set.
First, usually a pointer is used, since it saves the extra lookup when calling a VMT method, so is critical for performance.If you have 2^16 types max, that's 2 bytes per type instance. If you have a lot of instances and little data in each instance (like, 3 bytes), that is very significant.
Second, nobody builds in 65536 limitations in modern implementations anymore. It is 2011 now, and entry level PCs have 4GB.
Third, keep in mind a dynamic allocation will never be "just" 3 bytes. There is heapmanager administration overhead, minimal allocation size etc. Any dynamic allocation is probably at least 16 byte, probably more.
So any such scheme will be array based. Many arrays even, to even feel the memory impact, since extremely large arrays are often not possible. (you often can't allocate the available memory in one allocation in modern OSes)
So _if_ sb actually cares about this scenario, you need two object types, one with and one without VMTs.
Delphi solves this by separating "records" (structs/UDTs) from classes.
Records are just structs/UDTs and don't have the pointer to the VMT/classtype. Classes do (and in Delphi's case, are always dynamic)
More than memory savings, the existance of the non VMT record types are also useful to add behaviour to e.g. OS structs and other UDTs that can't be assumed to be under the language's control. (and thus can't be assumed to have a VMT pointer)
In the past, Delphi's precursor experimented with combining both aspects in a type (close to struct/UDT) that had a VMT depending on the fact if it had virtual methods, but that turned out to be a dead end. It let to complicated code on the compiler side and confusing behaviour for new beginners. When the object type got more complicate this was untenable afaik.
True. But the code-paths not, so due to differing codepath, the same procedure can be passed differing types to the same parameter.An inheritance chain makes sense if you could change the inheritance of a UDT on the fly... but, the inheritance scheme is set in stone when you compile.Usually it is a function, since you must walk the inheritance chain with a loop. (you start with the current class, and then walk till you find either the root object or the class that you are checking for)
More importantly that is the whole point, that base code can shuffle around objects without knowing which exact descendant is in it.
Knowing the whole codepath, though doable for isolated cases is equal to the halting problem, and not solvable for non trivial cases.
Many binary trees then, since there are two arguments typeof(x) IS TY, it is not a simple one dimensional arra lookup, since both type of X and TY vary.If it was a function, I'd imagine that it would/should use a binary lookup table that is created upon compilation, since that would always theoretically be faster than an inheritance chain.
Since inheritance trees are usually not deeper than 10-20 (and usually a lot less), this is not that important, performancewise. Keep in mind that many more complicated algorithms need more setup that might not be worthwhile with low N.
marcov explained it well. There's no bloat. If your class has virtual methods, there's no way to call the right method without including a hidden pointer to the virtual methods table. RTTI comes for free then, because that pointer is reused to locate the RTTI table.
If there's no virtual methods, you can force RTTI by extending the internal OBJECT class.
UDT's that don't have any of those (virtual methods or Object extension) will still as before, with no RTTI or hidden pointer.
If there's no virtual methods, you can force RTTI by extending the internal OBJECT class.
UDT's that don't have any of those (virtual methods or Object extension) will still as before, with no RTTI or hidden pointer.
I see, I see.
Ah, ok. Great. Just what I wanted to hear.UDT's that don't have any of those (virtual methods or Object extension) will still as before, with no RTTI or hidden pointer.
I don't see how it'd be many binary trees. Just one tree of parents per UDT... unless... you think IS would apply to grand-children, etc.? (maybe there needs to be one "IS" for children (ie: exact matches) and one for everyone else?)marcov wrote:Many binary trees then, since there are two arguments typeof(x) IS TY, it is not a simple one dimensional arra lookup, since both type of X and TY vary.Agamemnus wrote: If it was a function, I'd imagine that it would/should use a binary lookup table that is created upon compilation, since that would always theoretically be faster than an inheritance chain.