An object is declared just as a record would be declared; except that now,procedures and functions can be declared as if they were part of the record. Objects can ''inherit'' fields and methods from ''parent'' objects. This means that these fields and methods can be used as if they were included in the objects declared as a ''child'' object.
Furthermore, a concept of visibility is introduced: fields, procedures and functions can be delcared as public or private. By default, fields and methods are public, and are exported outside the current unit. Fields or methods that are declared private are only accessible in the current unit. The prototype declaration of an object is as follows:
As can be seen, as many private and public blocks as needed can be declared. Method definitions are normal function or procedure declarations. Fields cannot be declared after methods in the same block, i.e. the following will generate an error when compiling:
object types
Type MyObj = Object Procedure Doit; Field : Longint; end;But the following will be accepted:
Type MyObj = Object Public Procedure Doit; Private Field : Longint; end;because the field is in a different section.
Remark: Free Pascal also supports the packed object. This is the same as an object, only the elements (fields) of the object are byte-aligned, just as in the packed record. The declaration of a packed object is similar to the declaration of a packed record :
Type TObj = packed object; Constructor init; ... end; Pobj = ^TObj; Var PP : Pobj;Similarly, the {$PackRecords } directive acts on objects as well.
Type TAnObject = Object AField : Longint; Procedure AMethod; end; Var AnObject : TAnObject;then the following would be a valid assignment:
AnObject.AField := 0;Inside methods, fields can be accessed using the short identifier:
Procedure TAnObject.AMethod; begin ... AField := 0; ... end;Or, one can use the self identifier. The self identifier refers to the current instance of the object:
Procedure TAnObject.AMethod; begin ... Self.AField := 0; ... end;One cannot access fields that are in a private section of an object from outside the objects' methods. If this is attempted anyway, the compiler will complain about an unknown identifier. It is also possible to use the with statement with an object instance:
With AnObject do begin Afield := 12; AMethod; end;In this example, between the begin and end, it is as if AnObject was prepended to the Afield and Amethod identifiers. More about this in section With
A constructor/destructor pair is required if the object uses virtual methods. In the declaration of the object type, a simple identifier should be used for the name of the constuctor or destructor. When the constructor or destructor is implemented, A qualified method identifier should be used, i.e. an identifier of the form objectidentifier.methodidentifier. Free Pascal supports also the extended syntax of the New and Dispose procedures. In case a dynamic variable of an object type must be allocated the constructor's name can be specified in the call to New. The New is implemented as a function which returns a pointer to the instantiated object. Consider the following declarations:
Constructors and destructors
Type TObj = object; Constructor init; ... end; Pobj = ^TObj; Var PP : Pobj;Then the following 3 calls are equivalent:
pp := new (Pobj,Init);and
new(pp,init);and also
new (pp); pp^.init;In the last case, the compiler will issue a warning that the extended syntax of new and dispose must be used to generate instances of an object. It is possible to ignore this warning, but it's better programming practice to use the extended syntax to create instances of an object. Similarly, the Dispose procedure accepts the name of a destructor. The destructor will then be called, before removing the object from the heap.
In view of the compiler warning remark, the following chapter presents the Delphi approach to object-oriented programming, and may be considered a more natural way of object-oriented programming.
Type TParent = Object ... procedure Doit; ... end; PParent = ^TParent; TChild = Object(TParent) ... procedure Doit; ... end; PChild = ^TChild;As it is visible, both the parent and child objects have a method called Doit. Consider now the following declarations and calls:
Var ParentA,ParentB : PParent; Child : PChild; ParentA := New(PParent,Init); ParentB := New(PChild,Init); Child := New(PChild,Init); ParentA^.Doit; ParentB^.Doit; Child^.Doit;Of the three invocations of Doit, only the last one will call TChild.Doit, the other two calls will call TParent.Doit. This is because for static methods, the compiler determines at compile time which method should be called. Since ParentB is of type TParent, the compiler decides that it must be called with TParent.Doit, even though it will be created as a TChild. There may be times when the method that is actually called should depend on the actual type of the object at run-time. If so, the method cannot be a static method, but must be a virtual method.
Type TParent = Object ... procedure Doit;virtual; ... end; PParent = ^TParent; TChild = Object(TParent) ... procedure Doit;virtual; ... end; PChild = ^TChild;As it is visible, both the parent and child objects have a method called Doit. Consider now the following declarations and calls :
Var ParentA,ParentB : PParent; Child : PChild; ParentA := New(PParent,Init); ParentB := New(PChild,Init); Child := New(PChild,Init); ParentA^.Doit; ParentB^.Doit; Child^.Doit;Now, different methods will be called, depending on the actual run-time type of the object. For ParentA, nothing changes, since it is created as a TParent instance. For Child, the situation also doesn't change: it is again created as an instance of TChild. For ParentB however, the situation does change: Even though it was declared as a TParent, it is created as an instance of TChild. Now, when the program runs, before calling Doit, the program checks what the actual type of ParentB is, and only then decides which method must be called. Seeing that ParentB is of type TChild, TChild.Doit will be called. The code for this run-time checking of the actual type of an object is inserted by the compiler at compile time. The TChild.Doit is said to override the TParent.Doit. It is possible to acces the TParent.Doit from within the varTChild.Doit, with the inherited keyword:
Procedure TChild.Doit; begin inherited Doit; ... end;In the above example, when TChild.Doit is called, the first thing it does is call TParent.Doit. The inherited keyword cannot be used in static methods, only on virtual methods.
Type TParent = Object ... procedure Doit;virtual;abstract; ... end; PParent=^TParent; TChild = Object(TParent) ... procedure Doit;virtual; ... end; PChild = ^TChild;As it is visible, both the parent and child objects have a method called Doit. Consider now the following declarations and calls :
Var ParentA,ParentB : PParent; Child : PChild; ParentA := New(PParent,Init); ParentB := New(PChild,Init); Child := New(PChild,Init); ParentA^.Doit; ParentB^.Doit; Child^.Doit;First of all, Line 3 will generate a compiler error, stating that one cannot generate instances of objects with abstract methods: The compiler has detected that PParent points to an object which has an abstract method. Commenting line 3 would allow compilation of the program.
Remark: If an abstract method is overridden, The parent method cannot be called with inherited, since there is no parent method; The compiler will detect this, and complain about it, like this:
testo.pp(32,3) Error: Abstract methods can't be called directlyIf, through some mechanism, an abstract method is called at run-time, then a run-time error will occur. (run-time error 211, to be precise)