Beginners Guide to Types as Objects (Part 2)

Introduction.

Welcome to the second part of the tutorial, In this part I assume that you 
have read through Part 1, tried the examples, and experimented with some 
tests of your own.  I'll now cover some topics that I didn't include in 
Part 1.

Indexed property.

An indexed property is a property that behaves like an array, except that 
like in the case of a regular property, a function gets called when you 
access it.  I'll start with a very short example just to show the syntax.

   Type foo
     Declare Property bar(ByVal index As Integer, ByVal value As Integer)
     Declare Property bar(ByVal index As Integer) As Integer
     dummy As Integer
   End Type 

   Property foo.bar(ByVal index As Integer, ByVal value As Integer)
     Print "Property set, index=" & index & ", value=" & value 
   End Property

   Property foo.bar(ByVal index As Integer) As Integer
     Print "Property get, index=" & index
     Property = 0
   End Property

   Dim baz As foo

   baz.bar(0) = 42
   Print baz.bar(0)

As you can see, the declaration for our indexed property is very similar to 
a regular one, except this time we add an argument for the index.  I 
include a dummy integer member, because a type must have at least one data 
member.  As you can see, the property is then used with (0), to denote we 
want to get/set the zeroth index, just the same as we would for an ordinary 
array.  Now I'll show you a slightly more useful example, and I will 
describe it:

   Type foo
     Declare Constructor(ByVal num_elements As Integer)
     Declare Destructor()
     Declare Property bar(ByVal index As Integer, ByVal value As Integer)
     Declare Property bar(ByVal index As Integer) As Integer
   Private:
     x As Integer Ptr
     size As Integer
   End Type 

   Constructor foo(ByVal num_elements As Integer)
     x = CAllocate(num_elements * SizeOf(Integer))
     size = num_elements
   End Constructor

   Destructor foo()
     Deallocate(x)
   End Destructor

   Property foo.bar(ByVal index As Integer, ByVal value As Integer)
     If (index >= 0) And (index < size) Then
      x[index] = value
     Else
      Error 6
     End If
   End Property

   Property foo.bar(ByVal index As Integer) As Integer
     If (index >= 0) And (index < size) Then
      Property = x[index]
     Else
      Error 6
     End If
   End Property

   Dim baz As foo = foo(10)

   baz.bar(1) = 42
   Print baz.bar(1)

This time, I've added a constructor and destructor, which will allocate and 
deallocate a dynamic memory array, x, with the number of elements specified 
in the constructor.  Then when the property functions are invoked, I check 
if the index is within the bounds of the array, if it is then I perform the 
requested get or set.  If the index specified is out of bounds, then 'Error 
6' occurs, which is a way to abort the program with FB's 'out of bounds 
error', you could replace this with your own error handling routines.  Try 
this out, by changing the code 'baz.bar(1) = 42' to 'baz.bar(10) = 42', and 
you'll see it in action, as we specified only 10 elements (index 0-9)

Copy constructor.

A copy constructor is a special type of constructor, that is used to make a 
copy from an existing object.  When you write code like this:

   Type foo
   ...
   End Type

   Dim As foo a
   Dim As foo b = a

What happens is FreeBASIC automatically generates hidden code to construct 
b, by making a copy of a, this is the default copy constructor, and simply 
copies the data fields (members) across.  We can define our own copy 
constructor, here's just a brief snippet to show how we declare it.

   Type foo
     Declare Constructor(ByRef obj As foo)
     ...
   End Type

This will come in very useful for a reason I will now explain.

Deep/Shallow copy.

In that previous example, where we did the code 'Dim As foo b = a', that 
was what is known a shallow copy, it just simply copied the data fields 
across, however sometimes this is not desirable, imagine that one of the 
members is a pointer, what will happen is that the address that pointer 
points to will be copied across, so both objects will point to the same 
memory.  An example of this follows:

   Type foo
     x As Integer Ptr
   End Type

   Dim As foo a

   a.x = Allocate(SizeOf(Integer))
   *a.x = 42

   Dim As foo b = a

   Print *a.x, *b.x

   *a.x = 420

   Print *a.x, *b.x

   Deallocate(a.x)

As you see, because they both point to the same memory, changing one 
affects the other.  As explained in the previous section on the copy 
constructor, FreeBASIC creates the code to do shallow copies by default.  
This is also true if we do an assignment like:

   Dim As foo a, b

   b = a

In this case also, FreeBASIC creates a default assignment operator (Let) to 
perform a shallow copy.  In order to do deep copies, we need to define a 
copy constructor, and an assignment operator, that is overloaded to accept 
our type.  Here's an example using them.

   Type foo
     Declare Constructor()
     Declare Constructor(ByRef obj As foo)
     Declare Destructor()
     Declare Operator Let(ByRef obj As foo)
     x As Integer Ptr
   End Type

   Constructor foo()
     Print "Default ctor"
     x = CAllocate(SizeOf(Integer))
   End Constructor

   Constructor foo(ByRef obj As foo)
     Print "Copy ctor"
     x = CAllocate(SizeOf(Integer))
     *x = *obj.x
   End Constructor

   Destructor foo()
     Print "dtor"
     Deallocate(x)
   End Destructor

   Operator foo.Let(ByRef obj As foo)
     Print "Let"
     *x = *obj.x
   End Operator

   Dim As foo a

   *a.x = 42

   Dim As foo b = a 'Uses the copy constructor

   Print *a.x, *b.x

   *a.x = 420

   Print *a.x, *b.x

As you can see, the copy constructor gets called on the line 'Dim As foo b 
= a' and this time, we allocate some memory, and copy the data in the new 
copy constructor, so that we can adjust x in one object without it 
affecting the other.  If we change the main code as follows:

   Dim As foo a, b

   *a.x = 42
   b = a    'The assignment operator (Let) gets used this time.

   Print *a.x, *b.x

   *a.x = 420

   Print *a.x, *b.x

Then this time the assignment operator is used. Note that in the assignment 
operator code, we don't need to allocate any memory because it has already 
been allocated in the default constructor, we just need to copy the data 
across.  The line '*x = *obj.x' performs this copy.  If we had something 
more advanced, like a dynamic memory array, then we would need to 
reallocate the memory to be the correct size to fit the data being copied.  
Here's a more advanced version just to show that.

   Type foo
     Declare Constructor(ByVal num_elements As Integer)
     Declare Constructor(ByRef obj As foo)
     Declare Destructor()
     Declare Operator Let(ByRef obj As foo)
     x As Integer Ptr
     size As Integer
   End Type

   Constructor foo(ByVal num_elements As Integer)
     Print "Default ctor"
     x = CAllocate(SizeOf(Integer) * num_elements)
     size = num_elements
   End Constructor

   Constructor foo(ByRef obj As foo)
     Print "Copy ctor"
     x = CAllocate(SizeOf(Integer) * obj.size)
     size = obj.size
     For i As Integer = 0 To size - 1
      x[i] = obj.x[i]
     Next i
   End Constructor

   Destructor foo()
     Print "dtor"
     Deallocate(x)
   End Destructor

   Operator foo.Let(ByRef obj As foo)
     Print "Let"
     x = Reallocate(x, SizeOf(Integer) * obj.size)
     size = obj.size
     For i As Integer = 0 To size - 1
      x[i] = obj.x[i]
     Next i
   End Operator

   Dim As foo a = foo(5)

   a.x[0] = 42
   a.x[1] = 420

   Dim As foo b = a 'Uses the copy constructor

   Print a.x[0], a.x[1], b.x[0], b.x[1]

   b.x[0] = 10
   b.x[1] = 20

   Print a.x[0], a.x[1], b.x[0], b.x[1]

   b = a ' Now using the assignment operator

   Print a.x[0], a.x[1], b.x[0], b.x[1]

This may seem quite complex at first, it's worth just reading through it 
again, and experimenting with the examples, it's not too tricky once you're 
used to it.

Passing objects to functions ByVal

The idea of deep and shallow copies also applies to passing an object to a 
function by value.  When you pass a reference to an object (ByRef), you can 
modify the object, and these modifications will persist, however you can 
also pass by value, which will mean you can modify it without the changes 
persisting outside of the function.  When an object is passed by value to a 
function, a new copy is created, and if that object has a copy constructor, 
then this is invoked, if it doesn't, then the hidden shallow copy is 
performed.  Once the function ends, the objects destructor is called.

New/Delete

New and delete are special operators for dynamically allocating memory, 
then destroying it.  Because it is used with dynamic memory, it is used 
with pointers.  In all the examples up until now, we just used Dim to 
create our objects, this will create them on the stack, but by using new we 
can create them dynamically, which can allow more flexibility, just like 
using Allocate/DeAllocate with normal memory.  Another important thing 
about new, is that you don't need to check if the pointer is NULL after 
new, like you would if you did allocate.  If new fails, it causes an 
exception, which will end the program.  In later versions of FreeBASIC, it 
is likely that some kind of try..catch mechanism will be created to allow 
better exception handling, but as of the time of writing, this is not yet 
implemented.  

There are two different varieties of the new/delete.  The first type, 
creates just a single element or object, for example:

   Dim As Integer Ptr foo = New Integer

   *foo = 1
   Print *foo

   Delete foo

This will create a new Integer, then destroy it when we call delete.  
Remember I used ptr, because it is dynamic memory.  For simple data types 
you can also specify a default value, by placing it in parenthesis after 
the data type, ie:

   Dim As Integer Ptr foo = New Integer(42)

   Print *foo

   Delete foo

This also works for UDT's with just simple data fields:

   Type foo
     x As Integer
     y As Integer
   End Type

   Dim As foo Ptr bar = New foo(1, 2)
     
   Print bar->x, bar->y

   Delete bar

This initialization won't work for more complex types involving 
constructors/destructors etc., however a useful feature is that when using 
new/delete with objects, it also calls the constructor and destructor, try 
the following example:

   Type foo
     Declare Constructor()
     Declare Destructor()
     x As Integer
     y As Integer
   End Type

   Constructor foo()
     Print "ctor"
   End Constructor

   Destructor foo()
     Print "dtor"
   End Destructor

   Dim As foo Ptr bar = New foo

   Delete bar

You will see that the constructor and destructor for the object are called.

The second type of new/delete is for creating arrays, this time the number 
of elements is placed after the dataype in square brackets '[]'.  When 
using the array version, you must also use 'delete[]' instead of 'delete', 
so that FreeBASIC knows you are deleting an array, here is a simple example 
using the Integer type:

   Dim As Integer Ptr foo = New Integer[20]

   foo[1] = 1
   Print foo[1]

   Delete[] foo

This will create a dynamic array, with 20 Integer elements.  It should be 
noted this is different from Allocate, which takes the number of bytes as 
its argument; using new, you should specify the number of elements.  The 
array method works just the same for objects:

   Type foo
     Declare Constructor()
     Declare Destructor()
     x As Integer
     y As Integer
   End Type

   Constructor foo()
     Print "ctor"
   End Constructor

   Destructor foo()
     Print "dtor"
   End Destructor

   Dim As foo Ptr bar = New foo[3]

   Delete[] bar

When you run this code, you will see that three constructor/destructor 
pairs are called, because we created an array of three instances of foo.

You must remember to call Delete, or Delete[] for any memory allocated with 
New, or you will cause a memory leak, just like the way you must rememeber 
to call DeAllocate for any memory you allocate with the Allocate function.

Name Mangling

Name mangling, also known as name decoration, is something that happens 
behind the scenes, at a lower level, and as such is not essential to know 
about.  The reason for name mangling is to resolve problems that are 
involved with more than one function sharing the same name, which happens 
when functions are overloaded, or are part of a type.  Take for example the 
overloaded subs shown below:

   Sub foo Overload ()

   End Sub

   Sub foo(ByVal i As Integer)

   End Sub

If we didn't have name mangling, then both might be known at a lower level 
as FOO, which would cause a name clash, so they have to be decorated in 
order to know which one should be called when they are used.  For the first 
sub, the compiler actually creates a sub called _Z3FOOv, and for the second 
it creates a sub called _Z3FOOi.  The compiler then remembers these, and 
chooses the appropriate sub to call, depending on how you call it, for 
example 'foo()' will actually call _Z3FOOv, and 'foo(1)' will actually call 
_Z3FOOi.  We can spot something from this, that the 'v' stands for void (no 
argument), and 'i' stands for integer.  The full details of name mangling 
are quite complex, and vary between compilers, the Microsoft compilers use 
a different name mangling scheme to GNU compilers, and other compilers may 
use different schemes as well.  The main thing we need to know, is that 
FreeBASIC follows the GCC 3.x ABI (Application binary interface), meaning 
that any overloaded functions, or complex types will only be compatible 
with other compilers using the same scheme.  This is an unfortunate 
limitation, but it is not really a FreeBASIC problem, it is common of all 
the compilers that use advanced features, and even if all the compiler 
authors agreed on a common name mangling scheme, there are still other 
issues that would cause incompatability.

Implicit this

This again is not necessary to know about mostly, its something that 
happens behind the scenes at a lower level.  When you call a member 
function of an object, what actually happens is a hidden first parameter is 
passed, so that the function knows which instance of the object is being 
refered to.  This is also true for the 
property/constructor/destructor/operator members.  If we look at a very 
simple example:

   Type foo
     Declare Sub bar(ByVal n As Integer)
     x As Integer
   End Type

   Sub foo.bar(ByVal n As Integer)
     x = n
   End Sub

   Dim baz As foo
   baz.bar(5)

What actually happens behind the scenes is something essentially equivalent 
to this:

   Type foo
     x As Integer
   End Type

   Sub foo_bar(ByRef _this As foo, ByVal n As Integer)
     _this.x = n
   End Sub

   Dim baz As foo
   foo_bar(baz, 5)

This method using an explicit 'this' is often used in languages that do not 
have facilities to make it easier.  OOP is really just a set of concepts, 
that can be mostly coded in almost any language, some things are more 
difficult to implement, such as constructors, you would have to explicitly 
call a 'create', or 'init' function.  For some things such as 
private/public distinction, it is even more difficult or impossible because 
the compiler does not know to enforce them.  The reason for adding OOP 
features to a language is to hide a lot of this, and add syntactic sugar to 
make it simpler to do, or more transparent in use, such as the way we can 
use properties as if they were ordinary data members, rather than 
functions, which is what they really are.

Hints for debugging/profiling

When using GDB or other debuggers, and the gprof profiling tool, the 
information shown is in the C++ syntax, and all your variable names and 
other symbols are shown in upper case, here is just a very short overview 
to help you understand how these are shown:

Here's an example type:

   Type bar
     Declare Constructor()
     Declare Constructor(ByRef obj As bar)
     Declare Constructor(ByVal n As Integer)
     Declare Destructor()
     Declare Operator Cast() As Any Ptr
     Declare Operator Let(ByVal n As Integer)
     Declare Property foo(ByVal n As Integer)
     Declare Property foo() As Integer
     member As Any Ptr
   End Type

When using GDB, these will be shown as follows (note in C++ they use :: 
where we would use . (dot), '::' is known as the scope resolution 
operator):

  BAR::BAR()            - The default constructor
  BAR::BAR(BAR&)        - The copy constructor (& in C++ means a reference, 
like byref)
  BAR::BAR(int)         - The constructor taking an integer argument (note 
there is no special symbol to denote ByVal, as this is the default passing 
method in C/C++)
  BAR::~BAR()           - The destructor
  BAR::operator void*() - A cast to Any ptr (void is similar to Any, * 
means pointer)
  BAR::operator=(int)   - The assignment operator (Let), dentoted by '=', 
in C/C++ '=' is assignment, '==' is equality testing.
  BAR::FOO(int)         - Property foo setter, taking an integer argument
  BAR::FOO()            - Property foo getter

Member sub/functions are shown in the same way as properties, indexed 
properties are shown the same also, just with the extra argument for the 
index.

Here is how the FB data types will be shown:

Any ptr     - void *
ZString ptr - char *
String      - FBSTRING
byte        - signed char
ubyte       - bool
short       - short
ushort      - unsigned short
integer     - int
uinteger    - unsigned int
longint     - long long
ulongint    - unsigned long long

I hope that helps you get started with understanding how things are 
displayed in GDB/gprof, a little experimentation will always help.

More reading

http://www.freebasic.net/wiki/wikka.php?wakka=KeyPgOpNew
http://www.freebasic.net/wiki/wikka.php?wakka=KeyPgOpDelete
http://en.wikipedia.org/wiki/Copy_constructor
http://en.wikipedia.org/wiki/Object_copy
http://en.wikipedia.org/wiki/Name_mangling

Last Reviewed by Sancho3 on February 06, 2018