構築子、 '='
代入演算子、および
デストラクタ を適切に使います。これらは、オブジェクトを 構築/初期化、代入、および破棄するための特別なメンバー手続きです(パート #2)。
目次
1. コンパイラーの相互作用(デフォルト構築子、コピー構築子、コピー代入演算子を使う)
代入中やコピー構築中に、コンパイラは、コピー代入演算子、デフォルト構築子、およびコピー構築子の、さまざまな呼び出しと対話して、結果のオブジェクトのコピーを最適化し、ユーザーが提供する、メンバー手続きを最大限に活用します。(たぶん非網羅的)。
- For an assignment ('object2 = object1')
アルゴリズム:
' 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
- For a copy-construction ('Dim As typename object2 = object1')
アルゴリズム:
' 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
注:
次の判断のいずれかが検証された場合、型は、デフォルト構築子を持っています:
- 明示的なデフォルト構築子があります。
- その項目の少なくとも 1つに初期化子があります。
- 少なくともそのメンバーの 1つは、デフォルト構築子自体を持つ、オブジェクトです。
- そのベース(存在する場合)にはデフォルト構築子があります(組み込み 'Object' は、デフォルト構築子を持ちます)。
特定の条件下では、コピー構成に対して、明示的なコピー代入演算子を、呼び出すことができます(明示的なコピー構築子がない場合)。
しかし逆に、条件に関係なしで、明示的なコピー構築子が、代入に対して呼び出されることはありません(明示的なコピー代入演算子がない場合)。
- 代入またはコピー構築中に、default-constructor、copy-constructor、および copy-assignment 演算子と、この対話を、強調表示/検証する例
必要に応じて、最初の 7 つのコード行(
'#define UDT...')のそれぞれに独立して、コメントするかどうかによって、上記のアルゴリズムの、すべての条件をテストできます:
#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
出力例:
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. 適切なマナーの規則(構築子、コピー構築子、コピー代入演算子、解体子用)
構築子、コピー構築子、コピー代入演算子、およびデストラクタに影響する動作のリマインダー:
- 明示的な default-constructor を定義すると、コンパイラーによって作成された黙示的な default-constructor が、明示のものに置き換えられます。
- デフォルト以外の明示的な構築子を定義すると、コンパイラーによって構築される黙示的なデフォルト構築子は抑制されます。この正確なケースでは、デフォルト構築子はまったくありません!
- コンパイラーによって作成された、黙示的なコピー構築子(またはコピー代入演算子、または解体子)は、ユーザーが定義した、明示的なコピー構築子(またはコピー代入演算子、または解体子)に置き換えることができます。
- ただし、(デフォルト構築子とは対照的に)コピー構築子(またはコピー代入演算子または解体子)は常に存在し、コンパイラーによって黙示的に構築されるか、ユーザーによって明示的に定義されます。
- オブジェクトの構成がある場合、構成されたオブジェクトの型には、複合オブジェクトの宣言と一致する、黙示的または明示的な構築子が必要です。
- 型の継承がある場合、継承された型には、デフォルトの暗黙的または明示的な構築子が必要です(継承する型に、ユーザーによって明示的に定義された、定数コピー構築子がある場合を除く)。そして、これはオブジェクトが構築されていない場合でも、すべてです(コンパイラは、継承構造だけで、テストします)。
この動作は、
FreeBASIC に固有のようです。
上記のすべてから、コンパイルエラーとランタイムバグのほとんどを回避する「ゴールデンルール」を推測できます。
より安全なコードのための
Golden rules(コンパイル時と実行時):
- ユーザーが、構築子を明示的に定義する場合、default-constructor も明示的に定義することを強く推奨します。
- ユーザーが、コピー構築子またはコピー代入演算子または解体子を(空でないボディで)明示的に定義する必要がある場合は、3 つの同時(既知の「3つの規則」)に加えて、デフォルト構築子を定義することを推奨します(上のルールも適用されます)。
上記のすべてと特定のケース(継承あり)から、非常に安全な操作を可能にする、1つの「最大化ルール」を提案できます。
非常に安全なコード(コンパイル時および実行時)のための
Maximizing rule ですが、ときに、実際の制約を最大化することもあります:
- ユーザーが、任意の形式の構築子手続き(任意の形式の copy-constructor を含む)や、任意の形式の let-operator 手続きや、デストラクタ手続きを、明示的に定義する必要がある場合は、default-constructor と、標準の copy構築子と、標準の let 演算子と、デストラクタを、一緒に定義することを強く推奨します。
(これらの4つの明示的な手続きは、コンパイラからの対応する暗黙的な操作を、常に正しく多重定義するように、明示的に定義されています)
- 良いマナーのルールを、適用する例
文字列メンバーを持つ UDT 。上記の 4つの明示的な手続きを、定義する必要がある、文字列ポインターメンバーを持つUDTと比較します(そうでないと、プログラムがハングします):
'=== 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
出力例:
UDTstr UDTstr UDTstr
UDTptr2str UDTptr2str UDTptr2str
3. メンバーアクセス権への影響(構築子、コピー構築子、コピー代入演算子、解体子の宣言)
このようなメンバー手続きを宣言するときに、アクセス権を適用して、ユーザーが何か特定のコマンドを(Type の外から)実行できないようにできます。
デフォルトの、構築子、コピー構築子、コピー代入演算子、およびデストラクタは、コンパイラによって黙示的なバージョンを構築できる唯一のメンバー手続きです。
したがって、Type の外部からのユーザーによるアクセスを禁止する場合は、宣言時に、制限されたアクセス権(パブリックではない)を使った、明示的なバージョンで、これらを多重定義する必要があります。
さらに、このようなメンバー手続きは、プログラムで実際に呼び出されないと、実装されていない(本体を定義していない)ことがあります。
- 例 - ベース・オブジェクトの構築を禁止する必要がある、継承構造:
- Type の外部からのベース・オブジェクトの構築を禁止するには、制限されたアクセス権を使って、ベース型の default-constructor および copy-constructor を明示的に宣言する必要があります(暗黙バージョンを多重定義するため)。
- ベース型の default-constructor は、プライベート型として宣言できません。
派生型からアクセス可能でなければならず(派生オブジェクトを構築するため)、被保護として宣言されているからです。
実装が必要です。
- ベース型のコピー構築子は、この例では呼び出されないので、Private として宣言できます。
そのため、実装されていない場合があります。
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
- 例 - シングルトン(一枚札)構造(最大1つのオブジェクトがいつでも存在できます):
- シングルトンの構築は、静的手続き
'Singleton.create()' を呼び出すことによってのみ行う必要があります。
- したがって、
'Dim' や
'New' で、ユーザがオブジェクトが作成することを禁止するには、デフォルト構築子とコピー構築子を明示的に(暗黙バージョンを多重定義するために)プライベートとして制限されたアクセス権で、宣言する必要があります。
コピー構築子のみが呼び出されることはないため、実装はありません(デフォルト構築子は、
'New' によって Type 内から呼び出されます)。
- シングルトンの破棄は、静的手続き
'Singleton.suppress()' を呼び出すことによってのみ行う必要があります。
- そのため、
'Delete' により、ユーザがオブジェクトを破壊することを禁止するために、デストラクタを、プライベートとして制限付きアクセス権で、明示的に宣言する必要があります(暗黙バージョンをオーバーロードするため)。 (デストラクタは、
'Delete' によって Type 内から呼び出されるため、実装が必要です)。
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
出力例:
参照