In some OOP languages, declaring a variable of a class type creates an instance of that class. Delphi, instead, is based on an object reference model. The idea is that a variable of a class type, such as the TheDay variable in the preceding ViewDate example, does not hold the value of the object. Rather, it contains a reference, or a pointer, to indicate the memory location where the object has been stored. You can see this structure depicted in Figure 2.4.
The only problem with this approach is that when you declare a variable, you don't create an object in memory (which is inconsistent with all other variables, confusing new users of Delphi); you only reserve the memory location for a reference to an object. Object instances must be created manually, at least for the objects of the classes you define. Instances of the components you place on a form are built automatically by the Delphi library.
You've seen how to create an instance of an object by applying a constructor to its class. Once you have created an object and you've finished using it, you need to dispose of it (to avoid filling up memory you don't need any more, which causes what is known as a memory leak). This can be accomplished by calling the Free method. As long as you create objects when you need them and free them when you're finished with them, the object reference model works without a glitch. The object reference model has many consequences on assigning object and on managing memory, as you'll see in the next two sections.
If a variable holding an object only contains a reference to the object in memory, what happens if you copy the value of that variable? Suppose you write the BtnTodayClick method of the ViewDate example in the following way:
procedure TDateForm.BtnTodayClick(Sender: TObject); var NewDay: TDate; begin NewDay := TDate.Create; TheDay := NewDay; LabelDate.Caption := TheDay.GetText; end;
This code copies the memory address of the NewDay object to the TheDay variable (as shown in Figure 2.5); it doesn't copy the data of one object into the other. In this particular circumstance, this is not a very good approach—you keep allocating memory for a new object every time the button is clicked, but you never release the memory of the object the TheDay variable was previously pointing to.
This specific issue can be solved by freeing the old object, as in the following code (which is also simplified, without the use of an explicit variable for the newly created object):
procedure TDateForm.BtnTodayClick(Sender: TObject); begin TheDay.Free; TheDay := TDate.Create;
The important thing to keep in mind is that, when you assign an object to another object, Delphi copies the reference to the object in memory to the new object reference. You should not consider this a negative: In many cases, being able to define a variable referring to an existing object can be a plus. For example, you can store the object returned by accessing a property and use it in subsequent statements, as this code snippet indicates:
var ADay: TDate; begin ADay := UserInformation.GetBirthDate; // use a ADay
The same thing happens if you pass an object as a parameter to a function: You don't create a new object, but you refer to the same one in two different places in the code. For example, by writing this procedure and calling it as follows, you'll modify the Caption property of the Button1 object, not of a copy of its data in memory (which would be totally useless):
procedure CaptionPlus (Button: TButton); begin Button.Caption := Button.Caption + '+'; end; // call... CaptionPlus (Button1)
This means that the object is being passed by reference without the use of the var keyword and without any other obvious indication of the pass-by-reference semantic, which also confuses newcomers. What if you really want to change the data inside an existing object, so that it matches the data of another object? You have to copy each field of the object, which is possible only if they are all public, or provide a specific method to copy the internal data. Some classes of the VCL have an Assign method, which performs this copy operation. To be more precise, most of the VCL classes that inherit from TPersistent, but do not inherit from TComponent, have the Assign method. Other TComponent-derived classes have this method but raise an exception when it is called.
In the DateCopy example, I've added an Assign method to the TDate class and called it from the Today button, with the following code:
procedure TDate.Assign (Source: TDate); begin fDate := Source.fDate; end; procedure TDateForm.BtnTodayClick(Sender: TObject); var NewDay: TDate; begin NewDay := TDate.Create; TheDay.Assign(NewDay); LabelDate.Caption := TheDay.GetText; NewDay.Free; end;
Memory management in Delphi is subject to three rules, at least if you allow the system to work in harmony without Access Violations and without consuming unneeded memory:
Every object must be created before it can be used.
Every object must be destroyed after it has been used.
Every object must be destroyed only once.
Whether you must perform these operations in your code or can let Delphi handle memory management for you depends on the model you choose among the different approaches provided by Delphi.
Delphi supports three types of memory management for dynamic elements:
Every time you create an object explicitly in your application code, you should also free it (with the only exception of a handful of system objects and of objects that are used through interface references). If you fail to do so, the memory used by that object won't be released for other objects until the program terminates.
When you create a component, you can specify an owner component, passing the owner to the component constructor. The owner component (often a form) becomes responsible for destroying all the objects it owns. In other words, when you free a form, it frees all the components it owns. So, if you create a component and give it an owner, you don't have to remember to destroy it. This is the standard behavior of the components you create at design time by placing them on a form or data module. However, it is mandatory that you choose an owner that you can guarantee will be destroyed; for example, forms are generally owned by the global Application objects, which is destroyed by the library when the program ends.
When Delphi's RTL allocates memory for strings and dynamic arrays, it will automatically free the memory when the reference goes out of scope. You don't need to free a string: When it becomes unreachable, its memory is released.
If you call the Free method (or call the Destroy destructor) of an object twice, you get an error. However, if you remember to set the object to nil, you can call Free twice with no problem.
You might wonder why you can safely call Free if the object reference is nil, but you can't call Destroy. The reason is that Free is a known method at a given memory location, whereas the virtual function Destroy is determined at run time by looking at the type of the object—a very dangerous operation if the object no longer exists.
To sum things up, here are a couple of guidelines:
Always call Free to destroy objects, instead of calling the Destroy destructor.
Use FreeAndNil, or set object references to nil after calling Free, unless the reference is going out of scope immediately afterward.
In general, you can also check whether an object is nil by using the Assigned function. The following two statements are equivalent in most cases:
if Assigned (ADate) then ... if ADate <> nil then ...
Notice that these statements test only whether the pointer is not nil; they do not check whether it is a valid pointer. If you write the following code, the test will be satisfied, and you'll get an error on the line with the call to the object method:
ToDestroy.Free; if ToDestroy <> nil then ToDestroy.DoSomething;
It is important to realize that calling Free doesn't set the object to nil.