Beginners Guide to Types as Objects (Part 1)

Introduction

  This tutorial is aimed at people who want to know more about the new 
features added to Type, commonly being referred to as 'types as objects', 
and 'that OOP stuff'.  It aims to walk you through these new features, so 
is aimed at people who don't really understand it yet, but want to learn.  
A Type in FreeBASIC is an aggregate data type, like a struct in C, or a 
record in Pascal.  Here's just a short sample of typical Type usage.

   Type person_info
     first_name As String
     last_name As String
     house_number As Integer
     street_name As String
     town As String
   End Type

In this usage it's used as a kind of container for related data; in this 
example it could be as an entry in an address book.  With the new features, 
however, it can be used more like the class in C++, in that it can do much 
more than contain just simple fields of data.  It becomes a way to express 
an idea of an object, and this makes object oriented programming much 
simpler.  We will now look at these new features.

Property

We'll start by looking at property.  When you add a property to a Type, you 
access it as if it were an ordinary member, but what happens, is instead of 
just getting or setting a variable as normal, it calls a function instead.  
Take a look at this example:

   Type bar
     Declare Property x() As Integer
     Declare Property x(ByVal n As Integer)
     p_x As Integer
   End Type

   Property bar.x() As Integer
     Print "bar.x()"
     Property = p_x
   End Property

   Property bar.x(ByVal n As Integer)
     Print "bar.x(ByVal n As Integer)"
     p_x = n
   End Property

   '---

   Dim foo As bar

   foo.x = 5
   Print foo.x

We include in our Type some declarations for a Property; they are very 
similar to ordinary function declarations.  The first one declares a 
getter, the second a setter.  The p_x member is just an ordinary Integer 
member.

Next we write the code for the properties; again, the syntax is very 
similar to that of normal functions.  Note the way we return a value: 
instead of Function = value, we do Property = value.  You can do Return 
value as well.  Also note that you can refer to the member directly as p_x; 
you can also use the keyword this, for example this.p_x = n; using this 
isn't usually needed, but it can help in some ambiguous circumstances.

Then follows some testing code; this shows how we can use the property as 
if it were any ordinary member.  When you run the program it will also 
print to screen to show that the property get/set code has been called.

Now this code is fairly trivial, but as you get used to the idea you'll see 
it can be put to some good uses.  Imagine as an example you are writing a 
GUI, and the TYPE represents a button on the screen, you could do 
button.text = "Hello World!", and make the property code update the screen 
to show the changes.  Or maybe you are using the Type to maintain some kind 
of list; you could do list.size += 10 and then put some code in your 
property to make the list larger.

Constructor/Destructor

Constructors are functions that are called when the Type gets created - 
when you use Dim, for example.  A Destructor is a function that gets called 
when the Type goes out of scope; this could be when the program ends, for a 
Type in the main code, or when a function ends, for a local Type.  Look at 
the following example, expanded from the last.

   Type bar
     Declare Constructor()
     Declare Destructor()
     Declare Property x() As Integer
     Declare Property x(ByVal n As Integer)
     p_x As Integer Ptr
   End Type

   Constructor bar()
     Print "Constructor bar()"
     p_x = Allocate(SizeOf(Integer))
     *p_x = 10
   End Constructor

   Destructor bar()
     Print "Destructor bar()"
     Deallocate(p_x)
   End Destructor

   Property bar.x() As Integer
     Print "bar.x()"
     Property = *p_x
   End Property

   Property bar.x(ByVal n As Integer)
     Print "bar.x(ByVal n As Integer)"
     *p_x = n
   End Property

   '---

   Dim foo As bar

   Print foo.x
   foo.x = 5
   Print foo.x

Again the syntax is somewhat similar to normal functions.  Note that this 
time I changed p_x to be an Integer ptr.  The constructor then Allocates 
the memory for this when foo is created, and gives it a default value; then 
it De-Allocates this memory once it is destroyed.  So you can use 
Constructors and Destructors to set things up for you, then clean up once 
its finished with.  Again a trivial example, but bring back the example of 
some kind of list, and having it set the list up for you, and clean it up 
when it's finished with can be quite handy.

Methods

You can also have regular Subs and Functions inside your Type; in some 
terminology, these are referred to as methods.  We'll carry on our example:

   Type bar
     Declare Constructor()
     Declare Destructor()
     Declare Property x() As Integer
     Declare Property x(ByVal n As Integer)
     Declare Sub Mul5()
     Declare Function Addr() As Integer Ptr
     p_x As Integer Ptr
   End Type

   Constructor bar()
     Print "Constructor bar()"
     p_x = Allocate(SizeOf(Integer))
     *p_x = 10
   End Constructor

   Destructor bar()
     Print "Destructor bar()"
     Deallocate(p_x)
   End Destructor

   Property bar.x() As Integer
     Print "bar.x()"
     Property = *p_x
   End Property

   Property bar.x(ByVal n As Integer)
     Print "bar.x(ByVal n As Integer)"
     *p_x = n
   End Property

   Sub bar.mul5()
     *p_x *= 5
   End Sub

   Function bar.Addr() As Integer Ptr
     Function = p_x
   End Function

   '---

   Dim foo As bar

   Print foo.x
   foo.x = 5
   Print foo.x
   foo.mul5()
   Print foo.x
   Print "address p_x points to", foo.Addr()

So this time we added a Sub, that multiplies the integer pointed to by p_x 
by five, and a function that gets the memory address that the pointer 
holds.

Private/Public

By default all of the members of the bar type are public; that means that 
we can read/write or call them.  However, sometimes you might want to make 
them private.  Take for example our member p_x; we can currently do Print 
*foo.p_x, and it will allow us to print the value it points to.  We might 
want to make it private, so that only the members of the bar type (the 
constructor, destructor, property, and methods) can access it.  That way we 
can make sure we only deal with p_x by the ways we choose.  If for example 
we did 'DeAllocate(foo.p_x)' in our main code, then when the destructor 
runs, it would try to free it again, known as a 'double free'.  Change the 
Type declaration as follows:

   Type bar
     Declare Constructor()
     Declare Destructor()
     Declare Property x() As Integer
     Declare Property x(ByVal n As Integer)
     Declare Sub Mul5()
     Declare Function Addr() As Integer Ptr
   Private:
     p_x As Integer Ptr
   End Type

Now try adding Print *foo.p_x to the main code and compile it.  You'll get 
a message from fbc "error 173: Illegal member access, found 'p_x' in 'Print 
*foo.p_x'", showing that indeed the compiler is now enforcing the fact we 
made p_x private.  When you use private: or public:, any members following 
that statement follow the rule.  Here's a rather pointless example just to 
show the syntax:

   Type bar
   Private:
     a As Integer
     b As Integer
   Public:
     c As Integer
     d As Integer
   Private:
     e As Integer
   End Type

In the above type, the members a, b, and e are private; c and d are public.

Operator overloading

Operator overloading is a way of telling the compiler what to do in the 
case where we want to perform some kind of operation involving our Type.  
Take this example:

   Type bar
     n As Integer
   End Type

   Dim As bar x, y, z

   z = x + y

Now normally the compiler will throw an error when it sees this, as it has 
no idea how to add together two Types, but we can define what we want to 
happen.  Here's how:

   Type bar
     n As Integer
   End Type

   Operator +(ByRef lhs As bar, ByRef rhs As bar) As bar
     Operator = Type(lhs.n + rhs.n)
   End Operator

   Dim As bar x, y, z

   x.n = 5
   y.n = 10
   z = x + y
   Print z.n

In this code, I use lhs and rhs to refer to the left and right hand side 
operands of the operator.  Note also the expression type(lhs.n + rhs.n); 
this builds the Type that will be returned.  If you had a type like:

   Type bar
     x As Integer
     y As Integer
     z As Integer
   End Type

Then you would build it like type(xpart, ypart, zpart).

Most or all operators can be overloaded, and most of them are binary ops, 
meaning they have two operands like the + example above.  Some are unary 
ops having only a right hand side, like Not and unary minus.  They would be 
done like 'Operator Not(ByRef rhs As bar) As bar'.

There are some special cases where they have to be declared inside the Type
; these are the assignment operators and casts.

Assignment operators are things like += -= mod= etc, and also Let.  Let is 
used when you do an assignment like:

   Dim As bar foo
   Dim As Integer x
   foo = x

And casts are kind of the reverse; they are used when you cast to another 
datatype like:

   Dim As bar foo
   Dim As Integer x
   x = foo

Heres a short example using Let and Cast:

   Type bar
     n As Integer
     Declare Operator Let(ByRef rhs As Integer)
     Declare Operator Let(ByRef rhs As String)
     Declare Operator Cast() As String
   End Type

   Operator bar.Let(ByRef rhs As Integer)
     n = rhs
   End Operator

   Operator bar.Let(ByRef rhs As String)
     n = Val(rhs)
   End Operator

   Operator bar.Cast() As String
     Operator = Str(n)
   End Operator

   Operator +(ByRef lhs As bar, ByRef rhs As bar) As bar
     Operator = Type(lhs.n + rhs.n)
   End Operator

   Dim As bar x, y, z

   x = 5
   y = "10"
   z = x + y
   Print z

You need to have separate lets and casts for each data type you want to 
support. The operators that need declaring within the type are known as 
non-static, and the ones that don't are known as global.  There is a 
technical reason for this; the non-static ones need to know which instance 
(in the technical jargon, in our example above, we would say that x is an 
instance of bar) of the Type they are referring to, and this is 
accomplished by a hidden 'this' reference.  This hidden 'this' reference is 
how the other members like operators and methods know which instance of the 
Type the call refers to.  Most operators can be overloaded; here's a list 
of the ones that currently can be:

Specific ops:
cast, @, [], new, new[], delete, delete[], for, step, next
Assignment ops:
let, +=, -=, *=, &=, /=, \=, mod=, shl=, shr=, and=, or=, xor=, imp=, eqv=, 
^=
Unary ops:
-, not, *, ->, abs, sgn, fix, frac, int, exp, log, sin, asin, cos, acos, 
tan, atn, len
Binary ops:
+, -, *, &, /, \, mod, shl, shr, and, or, xor, imp, eqv, ^, =, <>, <, >, <=
, >=

Overloaded Constructors/Methods

As with normal functions, our Type's constructor and methods can be 
overloaded.  For constructors, this provides a way to specify details on 
how the instance should be constructed.  Here's a short example:

   Type bar
     Declare Constructor()
     Declare Constructor(ByVal initial_val As Integer)
     x As Integer
   End Type

   Constructor bar()
     x = 10
   End Constructor

   Constructor bar(ByVal initial_val As Integer)
     x = initial_val
   End Constructor

   Dim foo As bar
   Print foo.x

   Dim baz As bar = bar(25)
   Print baz.x

The first Constructor, that had no arguments, is known as the default 
constructor.  This sets up foo.x to an initial value of 10.  However, we 
have also specified another constructor that will accept an initial value.  
Note the way we ask for this to be called Dim baz As bar = bar(25).  You 
can also leave out the default constructor, and then you will always have 
to specify the initial value using the constructor that takes an argument. 
You can't have an overloaded destructor, because there's no way to manually 
choose which one would be called.

Overloaded methods are very similar:

   Type bar
     Declare Sub foo()
     Declare Sub foo(ByVal some_value As Integer)
     Declare Sub foo(ByRef some_value As String, ByVal some_other As Integer)
     x As Integer
   End Type

They work just they same as normal overloaded functions.

Closing

I hope this tutorial has been useful for you, although there are still a 
few things left to learn; if you've got this far, it shouldn't be too hard 
for you to pick them up.  There is some more information available in the 
wiki and on the forums, and also in part 2 of this tutorial, available here 
- Beginners Guide to Types as Objects (Part 2)

More reading

   * Property
   * Constructor
   * Destructor
   * Operator
   * This
   * Type
   * Types as Objects
   * Public:
   * Private:
   * Protected:

Last Reviewed by Sancho3 on February 06, 2018