Constructors, '=' Assignment-Operators, and Destructors (advanced, part #2)
Proper use of Constructors, '=' Assignment-Operators, and Destructors, which are the special member procedures for constructing/initializing, assigning, and destroying objects (part #2).
Table of Contents
1. Compiler interaction (with default-constructor, copy-constructor, and copy-assignment operator)
2. Rules of good manners (for constructors, copy-constructors, copy-assignment operators, and destructors)
3. Member access rights impact (on declaration of constructors, copy-constructors, copy-assignment operators, and destructors)
2. Rules of good manners (for constructors, copy-constructors, copy-assignment operators, and destructors)
3. Member access rights impact (on declaration of constructors, copy-constructors, copy-assignment operators, and destructors)
1. Compiler interaction (with default-constructor, copy-constructor, and copy-assignment operator)
During assignments or copy-constructions, the compiler interacts with the different calls of copy-assignment operators, default-constructors and copy-constructors, in order to optimize the copy for resulting objects, making the best use of the member procedures provided by the user (maybe non-exhaustively).
- For an assignment ('object2 = object1')
- For a copy-construction ('Dim As typename object2 = object1')
Algorithm:
' If (Type has a copy-assignment operator) Then ' | => the copy-assignment operator of Type is called ' Else ' | If (Type has a Base with default-constructor) AND (Type has a Base with copy-assignment operator) Then ' | | => the copy-assignment operator of Base is called (for Base fields) ' | | => a shallow-copy is done on only Type fields ' | Else ' | | => a full shallow-copy is done on all fields ' | End If ' End If
Algorithm:
' If (Type has a Base with default-constructor) Then ' | => the default-constructor of Base is called ' End If ' If (Type has a copy-constructor) Then ' | => the copy-constructor of Type is called ' Else ' | => the Type object is implicitly default-constructed (even if exists an explicit Type default-constructor) ' | If (Type has a copy-assignment operator) Then ' | | If (Type has an object field with a default-constructor) OR (Type has a Base with default-constructor) Then ' | | | => the copy-assignment operator of Type is called ' | | Else ' | | | => a full shallow-copy is done on all fields ' | | End If ' | Else ' | | If (Type has a Base with default-constructor) AND (Type has a Base with copy-assignment operator) Then ' | | | => the copy-assignment operator of Base is called (for Base fields) ' | | | => a shallow-copy is done on only Type fields ' | | Else ' | | | => a full shallow-copy is done on all fields ' | | End If ' | End If ' End If
Notes:
A Type has a default-constructor if one among the following propositions is verified:
But inversely, the explicit copy-constructor will never be called for an assignment (if there is not an explicit copy-assignment operator) regardless of the conditions.
- It has an explicit default-constructor
- One at least of its field has an initializer.
- One at least of its members is an object having a default-constructor itself.
- Its Base (if exists) has a default-constructor (the built-in 'Object' has a default-constructor).
Under certain conditions, the explicit copy-assignment operator may be called for a copy-construction (if there is not an explicit copy-constructor).- One at least of its field has an initializer.
- One at least of its members is an object having a default-constructor itself.
- Its Base (if exists) has a default-constructor (the built-in 'Object' has a default-constructor).
But inversely, the explicit copy-constructor will never be called for an assignment (if there is not an explicit copy-assignment operator) regardless of the conditions.
- Example to highlight/validate this interaction with default-constructor, copy-constructor, and copy-assignment operator, during assignments or copy-constructions
By commenting or not, as you want, independently each of the first 7 code lines ('#define UDT...'), you can test all conditions of the above algorithms:
#define UDTbase_copy_assignment_operator
#define UDTbase_default_constructor
#define UDTbase_copy_constructor
#define UDTderived_copy_assignment_operator
#define UDTderived_default_constructor
#define UDTderived_copy_constructor
#define UDTderived_object_field_with_constructor
Type UDTbase
#ifdef UDTbase_copy_assignment_operator
Declare Operator Let (ByRef u1 As UDTbase)
#endif
#ifdef UDTbase_copy_constructor
Declare Constructor ()
Declare Constructor (ByRef u1 As UDTbase)
#endif
#ifdef UDTbase_default_constructor
#ifndef UDTbase_copy_constructor
Declare Constructor ()
#endif
#endif
Declare Destructor ()
Dim As Integer i1
End Type
#ifdef UDTbase_copy_assignment_operator
Operator UDTbase.Let (ByRef u1 As UDTbase)
Print " => UDTbase.copy-assignment", @This & " from " & @u1
This.i1 = u1.i1
End Operator
#endif
#ifdef UDTbase_copy_constructor
Constructor UDTbase ()
Print " => UDTbase.default-constructor", @This
End Constructor
Constructor UDTbase (ByRef u1 As UDTbase)
Print " => UDTbase.copy-constructor", @This & " from " & @u1
This.i1 = u1.i1
End Constructor
#endif
#ifdef UDTbase_default_constructor
#ifndef UDTbase_copy_constructor
Constructor UDTbase ()
Print " => UDTbase.default-constructor", @This
End Constructor
#endif
#endif
Destructor UDTbase ()
Print " => UDTbase.destructor", , @This
End Destructor
Type UDTderived Extends UDTbase
#ifdef UDTderived_copy_assignment_operator
Declare Operator Let (ByRef u2 As UDTderived)
#endif
#ifdef UDTderived_copy_constructor
Declare Constructor ()
Declare Constructor (ByRef u2 As UDTderived)
#endif
#ifdef UDTderived_default_constructor
#ifndef UDTderived_copy_constructor
Declare Constructor ()
#endif
#endif
Declare Destructor ()
Dim As Integer i2
#ifdef UDTderived_object_field_with_constructor
Dim As String s2
#endif
End Type
#ifdef UDTderived_copy_assignment_operator
Operator UDTderived.Let (ByRef u2 As UDTderived)
Print " => UDTderived.copy-assignment", @This & " from " & @u2
This.i2 = u2.i2
This.i1 = u2.i1
End Operator
#endif
#ifdef UDTderived_copy_constructor
Constructor UDTderived ()
Print " => UDTderived.default-constructor", @This
End Constructor
Constructor UDTderived (ByRef u2 As UDTderived)
Print " => UDTderived.copy-constructor", @This & " from " & @u2
This.i2 = u2.i2
This.i1 = u2.i1
End Constructor
#endif
#ifdef UDTderived_default_constructor
#ifndef UDTderived_copy_constructor
Constructor UDTderived ()
Print " => UDTderived.default-constructor", @This
End Constructor
#endif
#endif
Destructor UDTderived ()
Print " => UDTderived.destructor", , @This
End Destructor
Scope
Print "Construction: 'Dim As UDTderived a, b : a.i1 = 1 : a.i2 = 2'"
Dim As UDTderived a, b : a.i1 = 1 : a.i2 = 2
Print " " & a.i1
Print " " & a.i2
Print
Print "Assignment: 'b = a'"
b = a
Print " " & b.i1
Print " " & b.i2
Print
Print "Copy-construction: 'Dim As UDTderived c = a'"
Dim As UDTderived c = a
Print " " & c.i1
Print " " & c.i2
Print
Print "Going out scope: 'End Scope'"
End Scope
Sleep
Output example:#define UDTbase_default_constructor
#define UDTbase_copy_constructor
#define UDTderived_copy_assignment_operator
#define UDTderived_default_constructor
#define UDTderived_copy_constructor
#define UDTderived_object_field_with_constructor
Type UDTbase
#ifdef UDTbase_copy_assignment_operator
Declare Operator Let (ByRef u1 As UDTbase)
#endif
#ifdef UDTbase_copy_constructor
Declare Constructor ()
Declare Constructor (ByRef u1 As UDTbase)
#endif
#ifdef UDTbase_default_constructor
#ifndef UDTbase_copy_constructor
Declare Constructor ()
#endif
#endif
Declare Destructor ()
Dim As Integer i1
End Type
#ifdef UDTbase_copy_assignment_operator
Operator UDTbase.Let (ByRef u1 As UDTbase)
Print " => UDTbase.copy-assignment", @This & " from " & @u1
This.i1 = u1.i1
End Operator
#endif
#ifdef UDTbase_copy_constructor
Constructor UDTbase ()
Print " => UDTbase.default-constructor", @This
End Constructor
Constructor UDTbase (ByRef u1 As UDTbase)
Print " => UDTbase.copy-constructor", @This & " from " & @u1
This.i1 = u1.i1
End Constructor
#endif
#ifdef UDTbase_default_constructor
#ifndef UDTbase_copy_constructor
Constructor UDTbase ()
Print " => UDTbase.default-constructor", @This
End Constructor
#endif
#endif
Destructor UDTbase ()
Print " => UDTbase.destructor", , @This
End Destructor
Type UDTderived Extends UDTbase
#ifdef UDTderived_copy_assignment_operator
Declare Operator Let (ByRef u2 As UDTderived)
#endif
#ifdef UDTderived_copy_constructor
Declare Constructor ()
Declare Constructor (ByRef u2 As UDTderived)
#endif
#ifdef UDTderived_default_constructor
#ifndef UDTderived_copy_constructor
Declare Constructor ()
#endif
#endif
Declare Destructor ()
Dim As Integer i2
#ifdef UDTderived_object_field_with_constructor
Dim As String s2
#endif
End Type
#ifdef UDTderived_copy_assignment_operator
Operator UDTderived.Let (ByRef u2 As UDTderived)
Print " => UDTderived.copy-assignment", @This & " from " & @u2
This.i2 = u2.i2
This.i1 = u2.i1
End Operator
#endif
#ifdef UDTderived_copy_constructor
Constructor UDTderived ()
Print " => UDTderived.default-constructor", @This
End Constructor
Constructor UDTderived (ByRef u2 As UDTderived)
Print " => UDTderived.copy-constructor", @This & " from " & @u2
This.i2 = u2.i2
This.i1 = u2.i1
End Constructor
#endif
#ifdef UDTderived_default_constructor
#ifndef UDTderived_copy_constructor
Constructor UDTderived ()
Print " => UDTderived.default-constructor", @This
End Constructor
#endif
#endif
Destructor UDTderived ()
Print " => UDTderived.destructor", , @This
End Destructor
Scope
Print "Construction: 'Dim As UDTderived a, b : a.i1 = 1 : a.i2 = 2'"
Dim As UDTderived a, b : a.i1 = 1 : a.i2 = 2
Print " " & a.i1
Print " " & a.i2
Print "Assignment: 'b = a'"
b = a
Print " " & b.i1
Print " " & b.i2
Print "Copy-construction: 'Dim As UDTderived c = a'"
Dim As UDTderived c = a
Print " " & c.i1
Print " " & c.i2
Print "Going out scope: 'End Scope'"
End Scope
Sleep
Construction: 'Dim As UDTderived a, b : a.i1 = 1 : a.i2 = 2' => UDTbase.default-constructor 1703576 => UDTderived.default-constructor 1703576 => UDTbase.default-constructor 1703556 => UDTderived.default-constructor 1703556 1 2 Assignment: 'b = a' => UDTderived.copy-assignment 1703556 from 1703576 1 2 Copy-construction: 'Dim As UDTderived c = a' => UDTbase.default-constructor 1703488 => UDTderived.copy-constructor 1703488 from 1703576 1 2 Going out scope: 'End Scope' => UDTderived.destructor 1703488 => UDTbase.destructor 1703488 => UDTderived.destructor 1703556 => UDTbase.destructor 1703556 => UDTderived.destructor 1703576 => UDTbase.destructor 1703576
2. Rules of good manners (for constructors, copy-constructors, copy-assignment operators, and destructors)
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.
From all the above, one can deduce 'golden rules' that avoid most of compilation errors and run-time bugs.- 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):
From all the above and more specific cases (with inheritance), one can propose one 'maximizing rule' that allows a very safer operating.
- 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).
- 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 define 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)
(these 4 explicit procedures are explicitly defined to always overload correctly the corresponding implicit operations from compiler)
- Example of applying rules of good manners
UDT with a string member, to compare to UDT with a string pointer member where the 4 explicit procedures above must be defined (otherwise program hangs):
'=== 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
Output example: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
UDTstr UDTstr UDTstr UDTptr2str UDTptr2str UDTptr2str
3. Member access rights impact (on declaration of constructors, copy-constructors, copy-assignment operators, and destructors)
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.
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:
- Example - Singleton structure (at most one object can exist at any time):
- In order to forbid any construction of base object 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.
- 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.
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
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
- 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').
- 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').
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
Output example: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
5122656 1234 0 5122656 0
See also
- Constructor, Destructor
- Operator =[>] (Assignment), Operator Let (Assignment)
- Constructors and Destructors (basics)
- Constructors, '=' Assignment-Operators, and Destructors (advanced, part #1)
Back to Programmer's Guide