Any type has an implicit default constructor and an implicit destructor provided by the compiler if they are needed and not overloaded by the user.
These constructors and destructors call the default constructors and destructors of the base types and type member data, but apart from that, they may eventually do nothing else.
It is therefore often necessary that user defines an explicit default constructor and an explicit destructor, in order to execute actions that must take place during the creation of an object and their destruction:
- For example, if the object contains dynamically allocated variables, it is necessary to reserve them memory when the object is created.
- At the destruction of the object, it is necessary to free the allocated memory.
Once the user defines any explicit constructor or explicit destructor, the compiler no longer automatically defines the implicit default constructor or the implicit destructor.
In particular, if the user only defines an explicit constructor taking parameters, it will no longer be possible to simply construct an object, without providing the parameters to this constructor, unless, of course, the user defines also an explicit default constructor (the one which does not take parameters).
1) Definition of constructors and destructors
- The constructor is defined as a normal member procedure. However, for the compiler to recognize it as a constructor, the following two conditions must be true:
- It must have the same name as the type.
- It must have no return data.
A constructor is called automatically when instantiating the object. The destructor is called automatically when it is destroyed.
This destruction occurs when outputting the current scope block for auto storage kind objects.
For dynamically allocated objects, the constructor and destructor are automatically called by expressions that use the New, New[], and Delete, and Delete[] operators. That is why it is recommended to use them instead of the Allocate and Deallocate functions to dynamically create objects.
Also, do not use Delete or Delete[] on 'Any Ptr' pointers, because the compiler must determine which destructor to call with the type of pointer.
The constructor is called after the memory allocation of the object and the destructor is called before this memory is freed. The management of the dynamic allocation of memory with the types is thus simplified.
In the case of member object fields, the order of construction is that of their declarations, and the order of destruction is the reverse. It is in this order that the constructors and destructors of each member object field are called.
The explicit constructors may have parameters. They can be overloaded, but not the explicit destructors. This is because in general one knows the context in which an object is created, but one cannot know the context in which it is destroyed: there can only be one destructor.
Constructors that do not take a parameter or with all parameters having a default value, automatically replace the default constructors defined by the compiler if there were no explicitly defined constructors in the types. This means that these constructors will be called automatically by the default constructors of the derived types. - Example - Constructors and destructor ('ZstringChain' type):
Code: Select all
Type ZstringChain '' implement a zstring chain Dim As Zstring Ptr pz '' define a pointer to the chain Declare Constructor () '' declare the explicit default constructor Declare Constructor (Byval size As Integer) '' declare the explicit constructor with as parameter the chain size Declare Destructor () '' declare the explicit destructor End Type Constructor ZstringChain () This.pz = 0 '' reset the chain pointer End Constructor Constructor ZstringChain (Byval size As Integer) This.pz = Callocate(size + 1, Sizeof(Zstring)) '' allocate memory for the chain End Constructor Destructor ZstringChain () If This.pz <> 0 Then Deallocate This.pz '' free the allocated memory if necessary This.pz = 0 '' reset the chain pointer End If End Destructor Dim As ZstringChain zc1 '' instantiate a non initialized chain : useless Dim As ZstringChain zc2 = ZstringChain(9) '' instantiate a szstring chain of 9 useful characters ' '' shortcut: Dim As ZstringChain zc2 = 9 *zc2.pz = "FreeBASIC" '' fill up the chain with 9 characters Print "zc2 chain:" Print "'" & *zc2.pz & "'" '' print the chain Sleep
The constructors will sometimes have to perform more complicated tasks than those given in this example. In general, they can do all the feasible operations in a normal member procedure, except using uninitialized data of course.Code: Select all
zc2 chain: 'FreeBASIC'
In particular, the data of inherited objects are not initialized as long as the constructors of the base types are not called. For this reason, the constructors of the base types must always be called before running the constructor of the type being instantiated.
If the constructors of the base types are not explicitly called, the compiler will call, by default, the constructors of the base types that do not take a parameter or whose parameters have a default value (and, if no such a constructor is explicitly defined in the base types, it will call the implicit default constructors of these types).
- How to call constructors and destructors of base types when instantiating and destroying a derived type instance?
The compiler cannot know which constructor to call among the different overloaded constructors potentially present. To call another constructor of a base type than the constructor taking no parameter, use the keyword 'Base ()' specifying the parameters to pass, and this, only authorized on the first code line of the calling constructor.
On the other hand, it is useless to specify the destructor to call, since this one is unique. The user must not call the destructors of the base types themselves, the compiler does it on its own by chaining the destructors. - Example - Explicit call of the base type constructor ('Child' type extends 'Parent' type):
Code: Select all
Type Parent '' declare the parent type Dim As Integer I Declare Constructor () Declare Constructor (Byval i0 As Integer) Declare Destructor () End Type Constructor Parent () '' define parent type constructor Print "Parent.Constructor()" End Constructor Constructor Parent (Byval i0 As Integer) '' define parent type constructor This.I = i0 Print "Parent.Constructor(Byval As Integer)" End Constructor Destructor Parent () '' define parent type destructor Print "Parent.Destructor()" End Destructor Type Child Extends Parent '' declare the child type Declare Constructor () Declare Destructor () End Type Constructor Child () '' define child type default constructor Base(123) '' authorize only on the first code line of the constructor body Print " Child.Constructor()" End Constructor Destructor Child () '' define child type destructor Print " Child.Destructor()" End Destructor Scope Dim As Child c Print End Scope Sleep
Code: Select all
Parent.Constructor(Byval As Integer) Child.Constructor() Child.Destructor() Parent.Destructor()
- If it was not specified that the constructor to be called for the base type was the constructor taking an Integer parameter, the compiler would have called the default constructor of the base type (in the above example, one can put in comment the line 'Base(123)' and execute again to see this different behavior).
When using subtype polymorphism (with inheritance), the object are manipulated through base type pointers or references:- If at least one derived type has an explicit destructor defined, all its base destructors must be virtual so that the destruction can start at this most derived type and works its way down to the last base type.
- To do this, it may be necessary to add virtual destructors with an empty body anywhere an explicit destruction was not yet required, in order to supersede each non-virtual implicit destructor built by the compiler.
- When a derived type has a base type where a default constructor or copy constructor or destructor is defined (implicitly or explicitly), the compiler defines a default constructor or copy constructor or destructor for that derived type.
- The built-in OBJECT type having a default-constructor and a copy-constructor both defined implicitly, so all types deriving (directly or indirectly) from OBJECT have at least implicitly a default constructor and copy constructor.
- It will sometimes be necessary to create a copy constructor. The purpose of this kind of constructor is to initialize an object when instantiating it from another object. Any type has if needed an implicit copy constructor automatically generated by the compiler, whose sole purpose is to copy the fields of the object to be copied one by one into the fields of the object to be instantiated. However, this implicit copy constructor will not always be enough, and the user will sometimes have to provide one explicitly.
This will be particularly the case when some data objects have been allocated dynamically (only member pointers in the type for object aggregation). A shallow copy of the fields of one object in another would only copy the pointers, not the data pointed. Thus, changing this data for one object would result in the modification of the other object's data, which would probably not be the desired effect.
The definition of copy constructors is like that of normal constructors. The name must be that of the type, and there must have no return data. In the parameter list, there must always be a reference on the object to be copied.
For the 'ZstringChain' type defined above, the user needs a copy constructor.
Its declaration is as follows:and its implementation as follows:Code: Select all
Declare Constructor (Byref zc As ZstringChain) '' declare the explicit copy constructor
Code: Select all
Constructor ZstringChain (Byref zc As ZstringChain) This.pz = Callocate(Len(*zc.pz) + 1, Sizeof(Zstring)) '' allocate memory for the new chain *This.pz = *zc.pz '' initialize the new chain End Constructor
- Example - Completed code with copy constructor ('ZstringChain' type):
Code: Select all
Type ZstringChain '' implement a zstring chain Dim As Zstring Ptr pz '' define a pointer to the chain Declare Constructor () '' declare the explicit default constructor Declare Constructor (Byval size As Integer) '' declare the explicit constructor with as parameter the chain size Declare Constructor (Byref zc As ZstringChain) '' declare the explicit copy constructor Declare Destructor () '' declare the explicit destructor End Type Constructor ZstringChain () This.pz = 0 '' reset the chain pointer End Constructor Constructor ZstringChain (Byval size As Integer) This.pz = Callocate(size + 1, Sizeof(Zstring)) '' allocate memory for the chain End Constructor Constructor ZstringChain (Byref zc As ZstringChain) This.pz = Callocate(Len(*zc.pz) + 1, Sizeof(Zstring)) '' allocate memory for the new chain *This.pz = *zc.pz '' initialize the new chain End Constructor Destructor ZstringChain () If This.pz <> 0 Then Deallocate This.pz '' free the allocated memory if necessary This.pz = 0 '' reset the chain pointer End If End Destructor Dim As ZstringChain zc1 '' instantiate a non initialized chain : useless Dim As ZstringChain zc2 = ZstringChain(9) '' instantiate a szstring chain of 9 useful characters ' '' shortcut: Dim As ZstringChain zc2 = 9 *zc2.pz = "FreeBASIC" '' fill up the chain with 9 characters Print "zc2 chain:" Print "'" & *zc2.pz & "'" '' print the chain Print Dim As ZstringChain zc3 = zc2 '' instantiate a new szstring chain by copy construction Print "zc3 chain (zc3 copy constructed from zc2):" Print "'" & *zc3.pz & "'" '' print the chain Print *zc3.pz = "modified" '' modify the new chain Print "zc3 chain (modified):" Print "'" & *zc3.pz & "'" '' print the new chain Print Print "zc2 chain:" Print "'" & *zc2.pz & "'" '' print the copied chain (not modified) Sleep
Code: Select all
zc2 chain: 'FreeBASIC' zc3 chain (zc3 copy constructed from zc2): 'FreeBASIC' zc3 chain (modified): 'modified' zc2 chain: 'FreeBASIC'
- For the 'ZstringChain' type defined above, the user needs also a copy assignment operator (see the 'rule of three' in a below paragraph).
Its declaration is as follows:and its implementation as follows (making copy assignment safe for self-assignment):Code: Select all
Declare Operator Let (Byref zc As ZstringChain) '' declare the explicit copy assignment operator
Code: Select all
Operator ZstringChain.Let (Byref zc As ZstringChain) If @zc <> @This Then '' avoid self assignment destroying the chain If This.pz <> 0 Then Deallocate This.pz '' free the allocated memory if necessary End If This.pz = Callocate(Len(*zc.pz) + 1, Sizeof(Zstring)) '' allocate memory for the new chain *This.pz = *zc.pz '' initialize the new chain End If End Operator
- Example - Final code with also copy assignment operator ('ZstringChain' type):
Code: Select all
Type ZstringChain '' implement a zstring chain Dim As Zstring Ptr pz '' define a pointer to the chain Declare Constructor () '' declare the explicit default constructor Declare Constructor (Byval size As Integer) '' declare the explicit constructor with as parameter the chain size Declare Constructor (Byref zc As ZstringChain) '' declare the explicit copy constructor Declare Operator Let (Byref zc As ZstringChain) '' declare the explicit copy assignment operator Declare Destructor () '' declare the explicit destructor End Type Constructor ZstringChain () This.pz = 0 '' reset the chain pointer End Constructor Constructor ZstringChain (Byval size As Integer) This.pz = Callocate(size + 1, Sizeof(Zstring)) '' allocate memory for the chain End Constructor Constructor ZstringChain (Byref zc As ZstringChain) This.pz = Callocate(Len(*zc.pz) + 1, Sizeof(Zstring)) '' allocate memory for the new chain *This.pz = *zc.pz '' initialize the new chain End Constructor Operator ZstringChain.Let (Byref zc As ZstringChain) If @zc <> @This Then '' avoid self assignment destroying the chain If This.pz <> 0 Then Deallocate This.pz '' free the allocated memory if necessary End If This.pz = Callocate(Len(*zc.pz) + 1, Sizeof(Zstring)) '' allocate memory for the new chain *This.pz = *zc.pz '' initialize the new chain End If End Operator Destructor ZstringChain () If This.pz <> 0 Then Deallocate This.pz '' free the allocated memory if necessary This.pz = 0 '' reset the chain pointer End If End Destructor Dim As ZstringChain zc1 '' instantiate a non initialized chain : useless Dim As ZstringChain zc2 = ZstringChain(9) '' instantiate a szstring chain of 9 useful characters ' '' shortcut: Dim As ZstringChain zc2 = 9 *zc2.pz = "FreeBASIC" '' fill up the chain with 9 characters Print "zc2 chain:" Print "'" & *zc2.pz & "'" '' print the chain Print Dim As ZstringChain zc3 = zc2 '' instantiate a new szstring chain by copy construction Print "zc3 chain (zc3 copy constructed from zc2):" Print "'" & *zc3.pz & "'" '' print the chain Print *zc3.pz = "modified" '' modify the new chain Print "zc3 chain (modified):" Print "'" & *zc3.pz & "'" '' print the new chain Print Print "zc2 chain:" Print "'" & *zc2.pz & "'" '' print the copied chain (not modified) Print zc3 = zc2 Print "zc3 chain (zc3 copy assigned from zc2):" Print "'" & *zc3.pz & "'" '' print the new chain Print *zc3.pz = "changed" '' modify the new chain Print "zc3 chain (changed):" Print "'" & *zc3.pz & "'" '' print the new chain Print Print "zc2 chain:" Print "'" & *zc2.pz & "'" '' print the copied chain (not modified) Sleep
Code: Select all
zc2 chain: 'FreeBASIC' zc3 chain (zc3 copy constructed from zc2): 'FreeBASIC' zc3 chain (modified): 'modified' zc2 chain: 'FreeBASIC' zc3 chain (zc3 copy assigned from zc2): 'FreeBASIC' zc3 chain (changed): 'changed' zc2 chain: 'FreeBASIC'
- Reminder of behaviors impacting constructors, copy constructors, copy assignment operators, and destructors:
- Defining an explicit default constructor replaces the implicit default constructor built by the compiler.
- Defining an explicit constructor other than the one default suppresses the implicit default constructor built by the compiler. In this precise case, there is no default constructor at all!
- The implicit copy constructor (or copy assignment operator, or destructor) built by the compiler can be replaced by an explicit copy constructor (or copy assignment operator, or destructor) defined by the user.
- But (as opposed to the default constructor), there is always a copy constructor (or a copy assignment operator or a destructor), either an implicit built by the compiler or an explicit defined by the user.
- When there is object composition, the composed object type must have an implicit or explicit constructor matching with the declaration of the compound object.
- When there is type inheritance, the inherited type must have a default implicit or explicit constructor (unless the inheriting type has a constant-copy constructor, explicitly defined by user), and all this even if no object is constructed (compiler test on the only inheritance structure). This behavior appears to be specific to FreeBASIC.
- Golden rules for a code safer (at compile-time and run-time):
- If the user explicitly defines any constructor, he is very strongly advised to also define explicitly the default constructor as well.
- If the user needs to explicitly define (with a non empty body) a copy constructor or a copy assignment operator or a destructor, it is better to define the 3 simultaneously (the known 'rule of three'), plus the default constructor (rule above also applied).
- Maximizing rule for a very safer code (at compile-time and run-time) but sometimes maximizing the real constraints:
- If the user needs to explicitly defines any form of constructor procedure (including any form of copy constructor) or any form of let-operator procedure or a destructor procedure, it is strongly recommended to define together the default constructor and the standard copy constructor and the standard let operator and the destructor.
(these 4 explicit procedures are explicitly defined to always overload correctly the corresponding implicit operations from compiler)
- If the user needs to explicitly defines any form of constructor procedure (including any form of copy constructor) or any form of let-operator procedure or a destructor procedure, it is strongly recommended to define together the default constructor and the standard copy constructor and the standard let operator and the destructor.
- Example - UDT with a string member <> UDT with a string pointer member where the above 4 explicit procedures must be defined (otherwise program hangs)
Code: Select all
'=== UDT with a string member ===================== Type UDTstr Dim As String s End Type '-------------------------------------------------- Dim As UDTstr us1 us1.s = "UDTstr" Dim As UDTstr us2 us2 = us1 Dim As UDTstr us3 = us2 Print us1.s, us1.s = "" Print us2.s, us2.s = "" Print us3.s '=== UDT with a string ptr member ================= Type UDTptr2str Dim As String Ptr ps Declare Constructor () Declare Destructor () Declare Operator Let (Byref ups As UDTptr2str) Declare Constructor (Byref ups As UDTptr2str) End Type Constructor UDTptr2str () This.ps = New String End Constructor Destructor UDTptr2str () Delete This.ps End Destructor Operator UDTptr2str.Let (Byref ups As UDTptr2str) *This.ps = *ups.ps End Operator Constructor UDTptr2str (Byref ups As UDTptr2str) Constructor() '' calling the default constructor This = ups '' calling the assignment operator End Constructor '-------------------------------------------------- Dim As UDTptr2str up1 *up1.ps = "UDTptr2str" Dim As UDTptr2str up2 up2 = up1 Dim As UDTptr2str up3 = up2 Print *up1.ps, *up1.ps = "" Print *up2.ps, *up2.ps = "" Print *up3.ps '================================================== Sleep
Code: Select all
UDTstr UDTstr UDTstr UDTptr2str UDTptr2str UDTptr2str
- Access rights can be applied when declaring such member procedures, to prohibit the user from performing certain specific commands (from the outside of type).
The default constructor, copy constructor, copy assignment operator, and destructor are the only member procedures which can have an implicit version built by the compiler.
So if one want to forbid their accesses by the user from the outside of the type, it is necessary to overload these by explicit versions with restricted access rights (not Public) when declaring. In addition, such member procedures may have no implementation (no body defining) if they are never actually called in the program. - Example - Inheritance structure where any base object construction must be forbidden:
- In order to forbid any construction of base objet from the outside of types, the default constructor and the copy constructor of base type must be explicitly declared (to overload their implicit versions) with restricted access rights.
- The base type default constructor cannot be declared as Private, because it must be accessible from the derived type (to construct a derived object), thus it is declared as Protected. It must have an implementation
- The base type copy constructor can be declared as Private because it is never called in this example. So it may have no implementation.
Code: Select all
Type Parent Public: Dim As Integer I Protected: Declare Constructor () Private: Declare Constructor (Byref p As Parent) End Type Constructor Parent End Constructor Type Child Extends Parent Public: Dim As Integer J End Type Dim As Child c1 Dim As Child C2 = c1 c2 = c1 'Dim As Parent p1 '' forbidden 'Dim As Parent p2 = c1 '' forbidden 'Dim As Parent Ptr pp1 = New Parent '' forbidden 'Dim As Parent Ptr pp2 = New Parent(c1) '' forbidden Sleep
- Example - Singleton structure (at most one object can exist at any time):
- The singleton construction must only be done by calling the static procedure 'Singleton.create()'.
- So, the default constructor and the copy constructor must be explicitly declared (to overload their implicit versions) with restricted access rights as Private, in order to forbid any object user creation by 'Dim' or 'New'. Only the copy constructor may have no implementation because it will never be called (the default constructor is called from inside the type by 'New').
- The singleton destruction must only be done by calling the static procedure 'Singleton.suppress()'.
- So, the destructor must be explicitly declared (to overload its implicit version) with restricted access rights as Private, in order to forbid any object user destruction by 'Delete' (the destructor must have implementation because it is called from inside the type by 'Delete').
Code: Select all
Type Singleton Public: Declare Static Function create () As Singleton Ptr Declare Static Sub suppress () Dim i As Integer Private: Static As Singleton Ptr ref Declare Constructor () Declare Constructor (Byref rhs As Singleton) Declare Destructor () End Type Dim As Singleton Ptr Singleton.ref = 0 Static Function Singleton.create () As Singleton Ptr If Singleton.ref = 0 Then Singleton.ref = New Singleton Return Singleton.ref Else Return 0 End If End Function Static Sub Singleton.suppress () If Singleton.ref > 0 Then Delete Singleton.ref Singleton.ref = 0 End If End Sub Constructor Singleton () End Constructor Destructor Singleton () End Destructor Dim As Singleton Ptr ps1 = Singleton.create() ps1->i = 1234 Print ps1, ps1->i Dim As Singleton Ptr ps2 = Singleton.create() Print ps2 Singleton.suppress() Dim As Singleton Ptr ps3 = Singleton.create() Print ps3, ps3->i Singleton.suppress() 'Delete ps3 '' forbidden 'Dim As Singleton s1 '' forbidden 'Dim As Singleton s2 = *ps3 '' forbidden 'Dim As Singleton Ptr ps4 = New Singleton '' forbiden 'Dim As Singleton Ptr ps5 = New Singleton(*ps3) '' forbidden Sleep
Code: Select all
5122656 1234 0 5122656 0
- How and Why to Define Constructors, Assignment-Operators, and Destructor, for UDTs in FB
- How and Why to make Abstraction by Object Encapsulation, with FB Syntax in UDTs (basics)
- How and Why to make Abstraction by Subtype Polymorphism, with FB Syntax in UDTs (advanced)