The Object Reference Model: 198 - 07: Objects
The Object Reference Model: 198 - 07: Objects
The notation used is nothing unusual, but it is powerful. We can write a complex
function (such as LeapYear) and then access its value for every TDate object as if it
were a primitive data type. Notice that ADay.LeapYear is an expression similar to
ADay.Year, although the first is a function call and the second a direct data access.
As we’ll see in Chapter 10, the notation used by Object Pascal to access properties is
again the same.
note Calls of methods with no parameters in most programming languages based on the C language
syntax require parenthesis, like in ADay.LeapYear(). This syntax is legal also in Object Pascal,
but rarely used. Methods with no parameters are generally called without the parenthesis. This is
very different from many languages in which a reference to a function or method with no paren-
thesis returns the function address. As we have see in the section “Procedural Types” in Chapter 4,
Object Pascal uses the same notation for calling a function or reading its address, depending on
the context of the expression.
note In my opinion, adopting the object reference model was one of the best design decisions made by
the compiler team in the early days of the language, when this model wasn't so common in pro-
gramming languages (in fact, at the time Java wasn't available and C# didn't exist).
This is why in these languages you need to explicitly create an object and assign it to
a variable, as objects are not automatically initialized. In other words, when you
declare a variable, you don’t create an object in memory, you only reserve the mem-
ory location for a reference to the object. Object instances must be created manually
and explicitly, at least for the objects of the classes you define. (In Object Pascal,
though, instances of components you place on a form are built automatically by the
run time library.)
In Object Pascal, to create an instance of an object, we can call its special Create
method, which is a constructor or another custom constructor defined by the class
itself. Here is the code again:
ADay := TDate.Create;
As you can see, the constructor is applied to the class (the type), not to the object
(the variable). That's because you are asking the class to create a new instance of its
type, and the result is a new object you'd generally assign to a variable.
Where does the Create method come from? It is a constructor of the class TObject,
from which all the other classes inherit, so it is universally available. It is very com-
mon to add custom constructors to your classes, though, as we'll see later in this
chapter.
var
ADay: TDate;
begin
// create
ADay := TDate.Create;
// use
...
// free the memory
ADay.Free;
end;
While this is the standard approach, the component library adds concepts like object
ownership to significantly lessen the impact of manual memory management, mak-
ing this a relatively simple issue to handle.
To further simplify memory management, the Object Pascal compilers for the
mobile platforms introduce an additional mechanism called Automatic Reference
Counting (or ARC). The ARC model uses reference counting and some other
advanced techniques to automatically dispose of objects that are not needed any
longer (or have no references pointing to them). So on these platforms, the call to
Free an object is generally superfluous: as the execution of the code above reaches
the end statement, the ADay variable goes out of scope and the referenced object is
automatically deleted. In any case, if you keep the Free statement in the code, it
does no harm at all and everything will work smoothly both with the desktop and
mobile compilers.
note Automatic Reference Counting (ARC) is a standard memory management technique for iOS
development in Objective-C and Swift, the preferred languages in Apple's Xcode. Object Pascal
borrowed from that model, including weak references and other elements, but extends it in a few
ways and has a very efficient implementation.
There is much more to memory management and ARC that you need to know, but
given this is a rather important topic and not a simple one, I decided to offer only a
short introduction here and have a full chapter focused on this topic, namely Chap-
ter 13. In that chapter I'll show you in detail the different techniques used on each
platform and those that work across all platforms.
What's Nil?
As I've mentioned, a variable can refer to an object of a given class. But it might not
be initialized yet, or the object it used to refer to might not be available any longer.
This is where you can use nil. This is a constant value indicating that the variable is
not assigned to any object (or it is assigned to a 0 memory location). When a variable
of a class type has no value, you can initialize it this way:
ADay := nil;
To check if an object has been assigned the variable, you can write either of the fol-
lowing expressions:
if ADay <> nil then ...
if Assigned (ADay) then ...
Do not make the mistake of assigning nil to an object to remove it from memory.
Setting an object to nil and freeing it are two different operations, at least on the
desktop compilers (ARC makes things a little different and it might free an object
when you set a reference to nil). So you often need to both free an object and set its
reference to nil, or call a special purpose procedure that does both operations at
once, called FreeAndNil. Again, more information and some actual demos will be
coming in Chapter 13.
note A consequence of this different memory management is that records lack inheritance and poly-
morphisms, two features we'll be focusing on in the next chapter.
For example, when you declare a record variable on the stack, you can start using it
right away, without having to call its constructor. This means record variables are
leaner (and more efficient) on the memory manager than regular objects, as they do
not participate in the management of the dynamic memory and ARC. These are the
key reasons for using records instead of objects for small and simple data structures.
Regarding the difference in the way records and objects are passed as parameters,
consider that the default is to make a full copy of the memory block representing the
record (including all of its data) or of the reference to the object (while the data is
not copied). Of course, you can use var or const record parameters to modify the
default behavior for passing record type parameters.