FreeBASIC マニュアルのトップに戻る

FreeBASIC ProPgCtorsAssignDtors

目次→教本→プログラマーのための案内Constructors, '=' Assignment-Operators, and Destructors (advanced, part #1)←オリジナル・サイト

構築子、 '=' 代入演算子、解体子(高度、パート #1) 左にメニュー・フレームが表示されていない場合は、ここをクリックして下さい

←リンク元に戻る プログラム開発関連に戻る

構築子、 '=' 代入演算子、およびデストラクタ を適切に使います。これらは、オブジェクトを 構築/初期化、代入、および破棄するための特別なメンバー手続きです(パート #1)。

序文:
Type に事前定義された名前を持つ、一部のメンバー手続きには、オブジェクトを構築、代入、破棄する、特定の役割があります。
これらは、構築子、'=' 代入演算子、およびデストラクタです。
これらのうち、一部は特別なものです。
これは、バージョンが必要な場合に、コンパイラーによって自動的にビルドされ、ユーザーが明示的に定義しないからです。
これらは、デフォルト構築子、コピー構築子、コピー代入演算子、およびデストラクタです。

したがって、オブジェクトの存続期間中に実行する必要があるアクションを実行するために、ユーザーが、独自の明示的なバージョンを定義することが、しばしば必要です:
- たとえば、オブジェクトに動的に割り当てられた変数が含まれる場合、オブジェクトの作成時に、そのメモリを予約する必要があります。
- オブジェクトの割り当てでは、多くの場合、メモリを再割り当てする必要があります。
- オブジェクトの破壊時に、割り当てられたメモリを解放する必要があります。

ユーザーが、明示的な構築子または明示的な解体子を定義すると、コンパイラーは、暗黙のデフォルト構築子または暗黙の解体子を自動的に定義しなくなります。
特に、ユーザーが、パラメーターを取得する明示的な構築子のみを定義する場合、もちろん、ユーザーが明示的なデフォルト構築子も定義しない限り、この構築子にパラメーターを提供せずに、単純にオブジェクトを構築できなくなります(パラメータを取らないもの)。

これらの特別なメンバー手続きを定義するには、型の継承と仮想性も考慮する必要があります。

すべてのメンバーとして、これらの特別なメンバー手続きは、パブリック、保護、プライベートのいずれかのアクセス可能性があります(ただし、プライベート・アクセスにはあまり関係がなく、直接または派生オブジェクトの構築を、完全に防ぐことさえできます)。

目次



1. 構築子と解体子
構築子は、オブジェクトをインスタンス化するときに、自動的に呼び出されます。
デストラクタは、オブジェクトが破棄されると、自動的に呼び出されます。
この破棄は、自動ストレージの種類のオブジェクトが、現在のスコープ・ブロックを出力するときに、発生します。

動的に割り当てられたオブジェクトの場合、構築子とデストラクタは、 'New', 'New[]', 'Delete', 'Delete[]' 演算子を使う式によって、自動的に呼び出されます。
そのため、 '[C|Re]]Allocate' と 'Deallocate' 関数の代わりに、これらを使って、オブジェクトを動的に作成することを推奨します。
また、'Any Ptr' ポインターで 'Delete' や 'Delete[]' を使わないでください。コンパイラーは、ポインターの型で呼び出す、デストラクタを決める必要があるからです。

構築子と解体子の定義
構築子は、オブジェクトのメモリ割り当て後に呼び出され、デストラクタは、このメモリが解放される前に呼び出されます。
したがって、型を使った、メモリの動的割り当ての管理が、簡素化されます。
メンバー・オブジェクト項目の場合、構築の順序は、宣言の順序であり、破棄の順序は、逆です。
各メンバー・オブジェクト項目の構築子とデストラクタが呼び出されるのは、この順序です。

明示的な構築子には、パラメーターがあります。
それらは多重定義できますが、明示的なデストラクタは多重定義できません。
これは一般に、オブジェクトが作成された文脈を知っているが、オブジェクトが破棄された文脈を知ることができないためです: デストラクタは 1つしか存在できません。
パラメーターを受け取らない構築子、またはすべてのパラメーターがデフォルト値を持つ構築子は、Types に明示的に定義された構築子がないと、コンパイラーによって定義されたデフォルトの構築子を自動的に置き換えます。
つまり、これらの構築子は、派生型の既定の構築子によって、自動的に呼び出されます。

例 - 構築子とデストラクタ('ZstringChain' 型):

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

出力:
zc2 chain:
'FreeBASIC'

構築子は、この例で示したタスクよりも、複雑なタスクを実行する必要がある場合があります。
通常、初期化されていないデータを使うことはできませんが、これ以外は、通常のメンバー手続きで実行できるすべての操作を実行できます。
特に、base-Types の構築子が呼び出されない限り、継承されたオブジェクトのデータは、初期化されません。
このため、インスタンス化される Type の構築子を実行する前に、base-Types の構築子を常に呼び出す必要があります。
base-Types の構築子が、明示的に呼び出されない場合、コンパイラーは、デフォルトで、パラメーターを受け取らない、またはパラメーターにデフォルト値を持つ base-Types の構築子を呼び出します(そして、このような構築子が base-Types で明示的に定義されていない場合、これらの型の暗黙のデフォルト構築子を呼び出します)。

構築子
構築子は、Type のインスタンスを初期化する、一種のメンバー手続きです。
構築子は、Type と同じ名前を持ち、戻り値はありません。
構築子は、任意の数のパラメーターを指定でき、Type は、任意の数の多重定義された構築子を指定できます。

構築子が明示的に定義されていない場合、コンパイラーは(必要に応じて)、パラメーターを受け取らないデフォルト構築子を生成します。
ユーザーは、独自の default-constructor を明示的に宣言および定義することにより、この動作をキャンセルできます。
ユーザーが、独自の構築子を定義する場合、黙示的なデフォルトの初期化操作は、ユーザーの構築子のコード実行の前に、常に行われます。

構築の順序:
- Type 構築子が呼び出されます。
- 基本型とそのメンバー構築子は、宣言の順序で呼び出されます。
- 型に仮想メンバー手続き(ベースから継承されたものを含む)がある場合、仮想ポインター(vptr)は、型の仮想テーブル(vtbl)を指すように、設定されます。
- Type 構築子手続きの本体が、実行されます。

デフォルト構築子:
default-constructor にはパラメーターがありません。
わずかに異なる規則に従います:
- default-constructor は、特別なメンバー関数の1つです。
- Type で構築子が明示的に宣言されていない場合、コンパイラーは default-constructor を提供します。
- デフォルト以外の構築子が宣言されている場合、コンパイラは、デフォルトの構築子を提供せず、ユーザーは、必要に応じてデフォルトの構築子を宣言する必要があります。

変換構築子:
Type に単一のパラメーターを持つ構築子がある場合、または少なくとも他のすべてのパラメーターにデフォルト値がある場合、最初の引数の型は、コンパイラーによって黙示的に Type の型に変換できます。
このような式では、'variable' という用語は、'typename(variable)' のショートカットと見なすことができます。

このような変換は、場合によっては便利ですが、多くの場合、可読性が低下する可能性があります(これらの場合、一致する構築子を、明示的に呼び出すことを推奨します)。

例 - 黙示的な変換:

Type UDT Extends Object
    Declare Constructor ()
    Declare Constructor (ByVal i0 As Integer)
    Declare Destructor ()
    Dim As Integer i
End Type

Constructor UDT ()
    Print "   => UDT.default-constructor", @This
End Constructor

Constructor UDT (ByVal i0 As Integer)
    Print "   => UDT.conversion-constructor", @This
    This.i = i0
End Constructor

Function RtnI (ByRef u As UDT) As Integer
    Return u.i
End Function
 
Destructor UDT ()
    Print "   => UDT.destructor", , @This
End Destructor

Scope
    Print "Construction: 'Dim As UDT u'"
    Dim As UDT u
    Print
    Print "Assignment: 'u = 123'"
    Print "      " & RtnI(123)            ''  RtnI(123): implicite conversion using the conversion-constructor,
    '                                     ''             short_cut of RtnI(UDT(123))
    Print
    Print "Going out scope: 'End Scope'"
End Scope

Sleep

出力例:
Construction: 'Dim As UDT u'
   => UDT.default-constructor             1703588

Assignment: 'u = 123'
   => UDT.conversion-constructor          1703580
	  123
   => UDT.destructor                      1703580

Going out scope: 'End Scope'
   => UDT.destructor                      1703588

しかし、このような変換は、コードに捉えにくいが重大なエラーを引き起こすことさえあります。

例 - コードの微妙な/深刻なエラー:

Type point2D
    Declare Constructor (ByVal x0 As Integer = 0, ByVal y0 As Integer = 0)
    Declare Operator Cast () As String
    Dim As Integer x, y
End Type

Constructor point2D (ByVal x0 As Integer = 0, ByVal y0 As Integer = 0)
    This.x = x0
    This.y = y0
End Constructor

Operator point2D.Cast () As String
    Return "(" & This.x & ", " & This.y & ")"
End Operator

Operator + (ByRef v0 As point2D, ByRef v1 As point2D) As point2D
    Return Type(v0.x + v1.x, v0.y + v1.y)
End Operator

Operator * (ByRef v0 As point2D, ByRef v1 As point2D) As Integer
    Return v0.x * v1.x + v0.y * v1.y
End Operator

Print "Construction of v1: 'Dim As point2D v1 = point2D(2, 3)'"
Dim As point2D v1 = point2D(2, 3)
Print "  => " & v1
Print
Print "Addition to v1: 'v1 + 4'"
Print "  => " & v1 + 4                  ''  4: implicite conversion using the conversion-constructor,
'                                       ''             short_cut of point2D(4, ) or point2D(4)
Print
Print "Multiplication of v1: 'v1 * 5'"
Print "  => " & v1 * 5                  ''  5: implicite conversion using the conversion-constructor,
'                                       ''             short_cut of point2D(5, ) or point2D(5)
Sleep

出力例:
Construction of v1: 'Dim As point2D v1 = point2D(2, 3)'
  => (2, 3)

Addition to v1: 'v1 + 4'
  => (6, 3)

Multiplication of v1: 'v1 * 5'
  => 10
この場合、コンパイラーは、その形式の1つを変換構築子として解釈できるので、オプションのパラメーターを持つ、マルチ-パラメーター構築子を宣言することは危険です。

回避策:デフォルトの構築子、およびマルチ-パラメーター構築子を定義しますが、オプションのパラメーター(あるいは、最初のパラメーターを考慮せずに、少なくとも 1つのオプションではないパラメーター)は使いません。

コピー構築子:
コピー構築子は、同じ型のオブジェクトへの参照を、入力として受け取り、それをコピーして新しいオブジェクトを作成する、特別なメンバー手続きです。
ユーザーが、コピー構築子を宣言しない場合、コンパイラーは(必要に応じて)コピー構築子を生成します。
コピー代入演算子の宣言(上記を参照)は、コピー構築子の、コンパイラーによる生成を削除しません。

コピー構築子を作成する必要がある場合があります。
この種の構築子の目的は、オブジェクトを別のオブジェクトからインスタンス化するときに、オブジェクトを初期化することです。
任意の Type には、コンパイラによって自動的に生成される、黙示的なコピー構築子があります。
その唯一の目的は、コピーするオブジェクトの項目を、インスタンス化するオブジェクトの項目に、1つずつコピーすることです。
ただし、この黙示的なコピー構築子では必ずしも十分ではなく、ユーザーが明示的に構築子を提供しなければならない場合があります。

これは、一部のデータオブジェクトが、動的に割り当てられた場合に特に当てはまります(オブジェクト集約のための Type 内のメンバーポインターのみ)。
あるオブジェクトの項目を別のオブジェクトに浅くコピー(shallow copy)すると、ポインタだけがコピーされ、ポイントされたデータはコピーされません。
したがって、この 1つのオブジェクトのデータを変更すると、他のオブジェクトのデータが変更されますが、これはおそらく望ましい結果ではありません。

シャローコピーとディープコピーの違い
https://kurochan-note.hatenablog.jp/entry/20110316/1300267023

ユーザーがコピー構築子を実装する場合は、コードの意味が明確になるように、コピー代入演算子も実装することを推奨します。

コピー構築子を呼び出すための明示的な構文に加えて、この構文は、オブジェクトが値によって手続きに渡されるとき、および関数が 'Return' キーワードを使って値によってオブジェクトを返すときにも、呼び出されます。

上記で定義された 'ZstringChain' Type の場合、ユーザーは、コピー構築子が必要です:

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

出力:
zc2 chain:
'FreeBASIC'

zc3 chain (zc3 copy constructed from zc2):
'FreeBASIC'

zc3 chain (modified):
'modified'

zc2 chain:
'FreeBASIC'

デストラクタ
デストラクタ機能は、構築子機能の逆です。
デストラクタは、オブジェクトが破棄されるときに呼び出されます。
デストラクタは通常、オブジェクトが不要になったときに "clean up" するために使われます。
デストラクタは、パラメータを持つことができません。

デストラクタが、明示的に定義されていない場合、コンパイラは、(必要に応じて)デストラクタを生成します。
ユーザーは、自身のデストラクタを明示的に宣言し定義することで、この動作を上書きできます。
ユーザーが、自身のデストラクタを定義する場合、黙示的な破棄操作は、常に、ユーザーの破棄コードの実行後に実行されます。

デストラクタは、次のイベントのいずれかが発生したときに、呼び出されます:
- New 演算子を使って作成されたオブジェクトは、Delete 演算子を使って明示的に破棄されます。
- ブロックスコープを持つローカルオブジェクトが、スコープ外になります。
- プログラムが終了し、グローバルか静的なオブジェクトが存在します。

ベースの Type か、データ・メンバーにアクセス可能なデストラクタがあり、Type がデストラクタを宣言していない場合、コンパイラはデストラクタを生成します。

破壊の順序:
- Type デストラクタが呼び出されます。
- Type に仮想メンバー手続き(ベースから継承されたものを含む)がある場合、仮想ポインター(vptr)は、Type の仮想テーブル(vtbl)を指すように、設定されます。
- Type デストラクタ手続きの本体が実行されます。
- 基本 Type のデストラクタは、宣言の逆順で呼び出されます。

継承時の構築子とデストラクタ
派生 Type インスタンスを、インスタンス化して破棄するときに、ベース型の構築子と解体子を呼び出す方法は?
コンパイラは、存在する可能性のある、さまざまな多重定義された構築子のうち、どの構築子を呼び出すかを知ることができません。
パラメーターを受け取らない構築子とは別の、ベース Type の構築子を呼び出すには、渡すパラメーターを指定するキーワード 'Base ()' を使います。 これは、呼び出し構築子の、最初のコード行でのみ許可されます。

一方、これは固有であるため、呼び出すデストラクタを指定することは無意味です。
ユーザーは、ベースの Type 自体の解体子を呼び出さないでください。コンパイラーは、解体子をチェーン化することによって独自に呼び出します。

例 - ベース Type 構築子の明示的な呼び出し('Child' Type は 'Parent' Type を拡張します):

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

出力:
Parent.Constructor(Byval As Integer)
  Child.Constructor()

  Child.Destructor()
Parent.Destructor()
ベース Type に対して呼び出される構築子が、Integer パラメーターを取る構築子であることが指定されていないと、コンパイラーは、ベース Type のデフォルト構築子を呼び出しました (上記の例では、 'Base(123)' の行にコメントを入れて、再度実行すると、この異なる動作を確認できます)。

Type が、複数の基本 Type (複数レベルの継承)から派生する場合、各派生 Type 構築子は、その直接の基本 Type の構築子を1つ、直接のベース Type の1つだけ、を明示的に呼び出すことができ、こうして、すべてがベース構築子のチェーンを構成します。

継承ポリモーフィズム(サブ型ポリモーフィズム)を使うとき、オブジェクトは、ベース Type ポインターまたは参照を介して、操作されます:
- 少なくとも1つの派生 Type に明示的なデストラクタが定義されている場合、そのすべてのベースデストラクタは、仮想である必要があります。これにより、破壊は、この最も派生した Type で始まり、最後のベース Type に至ります。
- これを行うには、コンパイラによって作成された非仮想の黙示的なデストラクタを置き換えるために、明示的な破棄がまだ必要ではない場所に、空のボディを持つ仮想デストラクタを追加する必要がある場合があります。

注:
- 派生 Type に、デフォルトの構築子またはコピー構築子または解体子が(暗黙的または明示的に)定義されている、ベース Type がある場合、コンパイラーは、その派生 Type のデフォルト構築子またはコピー構築子または解体子を定義します。
- 組み込みの 'Object' Type には、デフォルト構築子とコピー構築子の両方が暗黙的に定義されており、 したがって、'Object' から(直接的または間接的に)派生するすべての Type には、少なくとも黙示的にデフォルト構築子とコピー構築子があります。

構築子と解体子での仮想手続き呼び出し
通常、メンバー手続きを、構築子または解体子内から呼び出すのが安全です。 オブジェクトは、ユーザーコードの最初の行を実行する前に、完全にセットアップされる(仮想テーブルが初期化されるなど)からです。
ただし、構築または破棄中に、メンバー手続きが、抽象基本 Type の仮想メンバー手続きを呼び出すことは、潜在的に安全ではありません。

構築子や解体子で、仮想手続きを呼び出すときは、注意してください。 ベース構築子や解体子で呼び出される手続きは、派生 Type バージョンではなく、ベース Type バージョンであるためです。
このような仮想手続きが呼び出されると、呼び出される手続きは、構築子またはデストラクタ自身の Type に対して定義された(またはその Base から継承された)手続きです。

このページの頭に戻る


2. '=' 代入演算子
'=' 代入演算子の中には、同じ型のオブジェクトへの参照を入力として受け取り、そのコピーを作成する(新しいオブジェクトを作成せずに)固有のものがあります。
これがコピー代入演算子です。

コピー代入演算子
ユーザーが、コピー代入演算子を宣言しない場合、コンパイラーは、(必要に応じて)コピー代入演算子を生成します。
コピー構築子の宣言(上記を参照)は、コピー代入演算子の、コンパイラーによる生成を、削除しません。

コピー代入演算子は、黙示的なコピーでは不十分なときに、定義する必要があります。
これは、オブジェクトが、特別にコピーする必要がある、動的に割り当てられたメモリや他のリソースを、管理する場合に、発生します(たとえば、メンバーポインターが、動的に割り当てられたメモリを指す場合、暗黙的な '=' 代入演算子は、メモリを割り当てる代わりに、単にポインター値をコピーし、そして、データをコピーします)。

ユーザーが、コピー代入演算子を実装する場合は、コードの意味が明確になるように、コピー構築子も実装することを推奨します。

コピー代入演算子を呼び出すための、明示的な構文の使い方に加えて、この構文は、'Function =' キーワードを使って関数が値でオブジェクトを返すときにも、呼び出されます。

「byval arg」が default-construction + copy-assignment としてコーディングされる場合があるため (たとえば、 UDT にコピーコンストラクタがなく、デフォルトコンストラクタを持つオブジェクトフィールドまたはデフォルトコンストラクタを持つベースがある場合)、これは無限ループを引き起こします。

上記で定義された 'ZstringChain' Type の場合、ユーザーは、コピー代入演算子も必要です( 'advanced #2' ページの「3つの規則」を参照):

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

出力:
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'

このページの頭に戻る



3. 可変長配列を型のメンバーとして処理する
可変長配列は、可変長文字列のようなデフォルトのコピー構築子やコピー代入演算子がないため、可変長文字列のような疑似オブジェクトではありません。

しかし、可変長の配列が Type で宣言されている場合、コンパイラは、その Type 用にデフォルトのコピー構築子とデフォルトのコピー代入演算子を構築します。
これには、必要に応じて、出力配列のサイズを調整し、ソース配列からデータをコピーするすべてのコードが含まれます:
コンパイラが、自動的に配列のサイズ調整とコピーを行う例:

Type UDT
    Dim As Integer array(Any)
End Type

Dim As UDT u1, u2

ReDim u1.array(1 To 9)
For I As Integer = LBound(u1.array) To UBound(u1.array)
    u1.array(I) = I
Next I
 
u2 = u1
For I As Integer = LBound(u2.array) To UBound(u2.array)
    Print u2.array(I);
Next I
Print

Dim As UDT u3 = u1
For I As Integer = LBound(u3.array) To UBound(u3.array)
    Print u3.array(I);
Next I
Print

Sleep


出力:
 1 2 3 4 5 6 7 8 9
 1 2 3 4 5 6 7 8 9

ユーザーが、独自のコピー構築子とコピー代入演算子を指定したい場合(例えば、複雑なフィールド・メンバを追加で処理する場合)、上記のコンパイラによる自動的な配列のサイズ調整とコピーは解除されます:
明示的なコピー構築子とコピー代入演算子によって、コンパイラによる自動配列サイズ調整とコピーが破壊される例:

Type UDT
  Dim As Integer array(Any)
  'user fields
  Declare Constructor ()
  Declare Constructor (ByRef u As UDT)
  Declare Operator Let (ByRef u As UDT)
End Type

Constructor UDT ()
  'code for user fields in constructor
End Constructor

Constructor UDT (ByRef u As UDT)
  'code for user fields in copy-constructor
End Constructor

Operator UDT.Let (ByRef u As UDT)
  'code for user fields in copy-assignement operator
End Operator

Dim As UDT u1, u2

ReDim u1.array(1 To 9)
For I As Integer = LBound(u1.array) To UBound(u1.array)
  u1.array(I) = I
Next I
 
u2 = u1
For I As Integer = LBound(u2.array) To UBound(u2.array)
  Print u2.array(I);
Next I
Print

Dim As UDT u3 = u1
For I As Integer = LBound(u3.array) To UBound(u3.array)
  Print u3.array(I);
Next I
Print

Sleep


出力 (何も無し):
				

基本の可変長の配列メンバーは、暗黙の代入がないため、可変長の文字列メンバーのように疑似オブジェクトとしてコピーすることはできません(上記の例を参照すると、 'This.array = u.array' は禁止されています)。
ユーザーは、配列メンバーのサイズ変更とコピーを明示的にコーディングする必要があります:
配列のサイズ変更とコピーを、ユーザーのコピー構築子とコピー代入演算子で明示的に設定する例:

#include "crt/string.bi"

Type UDT
    Dim As Integer array(Any)
    'user fields
    Declare Constructor ()
    Declare Constructor (ByRef u As UDT)
    Declare Operator Let (ByRef u As UDT)
End Type

Constructor UDT ()
    'code for user fields in constructor
End Constructor

Constructor UDT (ByRef u As UDT)
    'code for user fields in copy-constructor
    If UBound(u.array) >= LBound(u.array) Then  '' explicit array sizing and copying
        ReDim This.array(LBound(u.array) To UBound(u.array))
        memcpy(@This.array(LBound(This.array)), @u.array(LBound(u.array)), (UBound(u.array) - LBound(u.array) + 1) * SizeOf(@u.array(LBound(u.array))))
    End If
End Constructor

Operator UDT.Let (ByRef u As UDT)
    'code for user fields in copy-assignement operator
    If @This <> @u And UBound(u.array) >= LBound(u.array) Then  '' explicit array sizing and copying
        ReDim This.array(LBound(u.array) To UBound(u.array))
        memcpy(@This.array(LBound(This.array)), @u.array(LBound(u.array)), (UBound(u.array) - LBound(u.array) + 1) * SizeOf(@u.array(LBound(u.array))))
    End If
End Operator

Dim As UDT u1, u2

ReDim u1.array(1 To 9)
For I As Integer = LBound(u1.array) To UBound(u1.array)
    u1.array(I) = I
Next I
 
u2 = u1
For I As Integer = LBound(u2.array) To UBound(u2.array)
    Print u2.array(I);
Next I
Print

Dim As UDT u3 = u1
For I As Integer = LBound(u3.array) To UBound(u3.array)
    Print u3.array(I);
Next I
Print

Sleep


出力:
 1 2 3 4 5 6 7 8 9
 1 2 3 4 5 6 7 8 9

もう1つの洗練された可能性は、このサイズ調整/コピーをコンパイラによって自動的にコーディングしたまま、単にそれを明示的に呼び出すことです。
これに関しては、メンバー配列を型自体のレベルに置くのではなく、別の特定の型に置き、継承するという、明らかな解決策があります(外見は全く同じです):
動的な配列メンバーを含む、ベース Type を使う例:

Type UDT0
    Dim As Integer array(Any)
End Type

Type UDT Extends UDT0
    'user fields
    Declare Constructor ()
    Declare Constructor (ByRef u As UDT)
    Declare Operator Let (ByRef u As UDT)
End Type

Constructor UDT ()
    'code for user fields in constructor
End Constructor

Constructor UDT (ByRef u As UDT)
    'code for user fields in copy-constructor
    Base(u)  '' inherited array sizing and copying from Base copy-constructor
End Constructor

Operator UDT.Let (ByRef u As UDT)
    'code for user fields in copy-assignement operator
    Cast(UDT0, This) = u  '' inherited array sizing and copying from Base copy-assignement operator
End Operator

Dim As UDT u1, u2

ReDim u1.array(1 To 9)
For I As Integer = LBound(u1.array) To UBound(u1.array)
    u1.array(I) = I
Next I
 
u2 = u1
For I As Integer = LBound(u2.array) To UBound(u2.array)
    Print u2.array(I);
Next I
Print

Dim As UDT u3 = u1
For I As Integer = LBound(u3.array) To UBound(u3.array)
    Print u3.array(I);
Next I
Print

Sleep

出力:
 1 2 3 4 5 6 7 8 9
 1 2 3 4 5 6 7 8 9

このページの頭に戻る



参照
プログラマーのための案内に戻る
←リンク元に戻る プログラム開発関連に戻る
ページ歴史:2022-08-17 09:21:34
日本語翻訳:WATANABE Makoto、原文著作者:fxm

ホームページのトップに戻る

表示-非営利-継承