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

FreeBASIC オブジェクトとしての型 入門(2)

目次→教本→いっしょに学ぼうBeginners Guide to Types as Objects (Part 2)←オリジナル・サイト

オブジェクトとしての型 入門(2) 左にメニュー・フレームが表示されていない場合は、ここをクリックして下さい

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


序論

チュートリアルの第二部をご覧いただき、ありがとうございます。ここでは、あなたは (1) を、通して読んでいただいたものと考えます。あなたは、例のコードを試しながら、あなた自身のテストも実験したでしょう。
私は、ここでは、 (1) に含んでいない、いくつかの話題をカバーするつもりです。


索引付き特性

索引をつけられた property は、配列のようにふるまう property です。通常の property の場合には、property にアクセスすると、関数が呼ばれるようにふるまう点だけが、配列とは異なります。
構文を示すために、非常に短い例から始めましょう。

Type foo
  Declare Property bar (ByVal index As Integer, ByVal value As Integer)
  Declare Property bar(ByVal index As Integer) As Integer
  dummy As Integer
End Type

Property foo.bar (ByVal index As Integer, ByVal value As Integer)
  Print "Property set, index=" & index & ", value=" & value
End Property

Property foo.bar(ByVal index As Integer) As Integer
  Print "Property get, index=" & index
  Property = 0
End Property

Dim baz As foo

baz.bar(0) = 42
Print baz.bar (0)
Sleep


お分かりのように、索引をつけられた property のための宣言は、通常の property とほとんど同じです。この時、インデックスのための引数を加える点だけが、異なります。
私は、ダミーの整数メンバーを入れます。理由は、型には、少なくとも 1つのデータ・メンバーが必要だからです。
お分かりのように、property は (0) で使われます。それは、ちょうど私たちが、普通の配列のためにそうするのと同じように、私たちは、零番目のインデックスを get/set したいことを意味します。
今、私は、もう少し役に立つ例を示します。そして、説明を加えます:

Type foo
  Declare Constructor (ByVal num_elements As Integer)
  Declare Destructor ()
  Declare Property bar (ByVal index As Integer, ByVal value As Integer)
  Declare Property bar(ByVal index As Integer) As Integer
Private:
  x As Integer Ptr
  size As Integer
End Type

Constructor foo (ByVal num_elements As Integer)
  x = CAllocate (num_elements * SizeOf(Integer))
  size = num_elements
End Constructor

Destructor foo ()
  Deallocate(x)
End Destructor

Property foo.bar (ByVal index As Integer, ByVal value As Integer)
  If (index >= 0) And (index < size) Then
    x[index] = value
  Else
    Error 6
  End If
End Property

Property foo.bar(ByVal index As Integer) As Integer
  If (index >= 0) And (index < size) Then
    Property = x [index]
  Else
    Error 6
  End If
End Property

Dim baz As foo = foo (10)

baz.bar(1) = 42
Print baz.bar (1)
Sleep


今回、私は、構築子と解体子を加えました。構築子と解体子は、動的メモリ配列 x を、割り当て、そして、割り当て解除します。素子数は、構築子で指定します。
次に、property の関数が呼び出されるとき、インデックスが配列の領域の中にあるかをチェックします。インデックスが配列の領域の中にあれば、要求された get または set を実行します。
指定されたインデックスが、区域外にあると、'Error 6' が発生します。 'Error 6' は、FB の 'out of bounds error' でプログラムを中止する方法です。あなたは、これを、あなた自身のエラー処理ルーチンに取り替えることができるのです。
このコードを、'baz.bar(1) = 42' から 'baz.bar(10) = 42' に変えて、試してみてください。私たちは、10 (index 0-9) の要素だけを指定したので、エラー処理の動作を見てください。


コピー構築子

コピー構築子は、構築子の特別な種類です。これは、既存のオブジェクトを複写するために使います。
コード書くときには、下のように書いてください。

Type foo
...
End Type

Dim As foo a
Dim As foo b = a


起こることは、FreeBASIC は、a のコピーを作って、b を組み立てる、隠れたコードを自動的に発生させるということです。これは、デフォルト・コピー構築子であり、単純に、データ項目(メンバー)の全体を、コピーします。
私たちは、私たち自身のコピー構築子を定義できます。下は、この宣言方法を示す、短いコードです。

Type foo
  Declare Constructor (ByRef obj As foo)
  ...
End Type


これは、私が現在説明しようとする理由に、非常に役立つものになります。

Deep/Shallow コピー

注: 二つの参照が、物理的に同一の物を参照します。(浅いコピー:Shallow Copy)
新たな複合オブジェクトを作成し、その後 (可能な限り) 元のオブジェクト中に見つかったオブジェクトに対する 参照 を挿入します。

物理的な内容をすべてコピーします。(深いコピー:Deep Copy)
新たな複合オブジェクトを作成し、その後元のオブジェクト中に見つかったオブジェクトの コピー を挿入します。

前の例で、コード 'Dim As foo b = a' を実行しました。これは、浅い(Shallow)コピーとして知られていることです。これは、単純に、データ項目全体に渡って、コピーします。しかし、これは望ましくない場合が、時々あります。メンバーの一つがポインタであることを、想像してください。この場合には、ポインタが示すアドレスが、そのままコピーされてしまいます。このため、両方のオブジェクトが、同じメモリを示すことになるのです。
下のコードは、この例です:

Type foo
  x As Integer Ptr
End Type

Dim As foo a

a.x = Allocate (SizeOf(Integer))
*a.x = 42

Dim As foo b = a

Print *a.x, *b.x

*a.x = 420

Print *a.x, *b.x

Deallocate(a.x)
Sleep


ご覧のように、これらの両方が同じメモリを示すので、1つを変えると、もう片方も影響されます。
コピー構築子の、前項で説明したように、FreeBASIC は、デフォルトで、浅い(shallow)コピーをするために、コードを作成します。
また、私たちが下のように代入するなら、これも本当です。

Dim As foo a, b

b = a


この場合も、FreeBASICは、浅いコピーを実行するために、デフォルト代入演算子 (Let) を生成します。
深い(deep)コピーをするために、私たちは、コピー構築子、および代入演算子を定義する必要があります。代入演算子は、私たちの型を受け入れるために、多重定義されます。
深い(deep)コピーをすると、参照先のインスタンスも複製します。
下は、これらを使う例です。

Type foo
  Declare Constructor ()
  Declare Constructor (ByRef obj As foo)
  Declare Destructor ()
  Declare Operator Let (ByRef obj As foo)
  x As Integer Ptr
End Type

Constructor foo ()
  Print "デフォルト構築子"
  x = CAllocate (SizeOf(Integer))
End Constructor

Constructor foo (ByRef obj As foo)
  Print "コピー構築子"
  x = CAllocate (SizeOf(Integer))
  *x = *obj.x
End Constructor

Destructor foo ()
  Print "解体子"
  Deallocate(x)
End Destructor

Operator foo.Let (ByRef obj As foo)
  Print "代入"
  *x = *obj.x
End Operator

Dim As foo a

*a.x = 42

Dim As foo b = a '構築子のコピーを使う

Print *a.x, *b.x

*a.x = 420

Print *a.x, *b.x
Sleep


お分かりのように、コピー構築子は、'Dim As foo b = a' で呼ばれます。私たちは、今回、何らかのメモリを割り当てて、データを、新しいコピー構築子に、コピーします。それで、私たちは、ひとつのオブジェクトの x を、他に影響を及ぼさないで変更できるようになります。
私たちがメイン・コードを変えるなら、下のようになります。

Dim As foo a, b

*a.x = 42
b = a    '今回、代入演算子 (Let) が、使われます

Print *a.x, *b.x

*a.x = 420

Print *a.x, *b.x


ここでは、代入演算子が使われています。
代入演算子コードでは、私たちは、どんなメモリも割り当てる必要はない、ということに、注意して下さい。 なぜなら、デフォルト構築子に、既にメモリを割り当ててあるからです。私たちに必要なのは、データを全て、コピーすることだけです。
'*x = *obj.x' の行は、このデータ・コピーを実行します。
動的メモリ配列のように、何かより高度なものがあるなら、メモリを、コピーされるデータの的確なサイズに合致するように、再割当てすることも、可能になるでしょう。
下は、まさしくこれを示す、より高度なバージョンです。

Type foo
  Declare Constructor (ByVal num_elements As Integer)
  Declare Constructor (ByRef obj As foo)
  Declare Destructor ()
  Declare Operator Let (ByRef obj As foo)
  x As Integer Ptr
  size As Integer
End Type

Constructor foo (ByVal num_elements As Integer)
  Print "デフォルト構築子"
  x = CAllocate (SizeOf(Integer) * num_elements)
  size = num_elements
End Constructor

Constructor foo (ByRef obj As foo)
  Print "コピー構築子"
  x = CAllocate (SizeOf(Integer) * obj.size)
  size = obj.size
  For i As Integer = 0 To size - 1
    x[i] = obj.x [i]
  Next i
End Constructor

Destructor foo ()
  Print "解体子"
  Deallocate(x)
End Destructor

Operator foo.Let (ByRef obj As foo)
  Print "代入"
  x = Reallocate (x, SizeOf(Integer) * obj.size)
  size = obj.size
  For i As Integer = 0 To size - 1
    x[i] = obj.x [i]
  Next i
End Operator

Dim As foo a = foo (5)

a.x[0] = 42
a.x[1] = 420

Dim As foo b = a '構築子のコピーを使う

Print a.x[0], a.x[1], b.x[0], b.x [1]

b.x[0] = 10
b.x[1] = 20

Print a.x[0], a.x[1], b.x[0], b.x [1]

b = a ' ここで、代入演算子を使います

Print a.x[0], a.x[1], b.x[0], b.x [1]
Sleep


上のコードは、最初は、かなり複雑に見えるかもしれません。しかし、再びそれを読み通して、例を実験する価値があります。あなたがいったんそれに慣れると、これはそれほど扱いにくくはありません。


オブジェクトを、関数に ByVal で渡す


深い(deep) と 浅い(shallow) のコピーの考えは、また、オブジェクトを関数に値で渡すために、適用されます。
参照を、オブジェクト (ByRef) に渡すとき、オブジェクトを変更できます。そして、これらの変更は持続します。
しかし、あなたはまた、値で渡すことができます。値で渡すとは、変更を関数の外で持続させることなく、値を変更できることを意味します。
オブジェクトが、値で関数に渡されるとき、新しいコピーが作成されます。そして、そのオブジェクトが、コピー構築子を持つなら、これが呼び出されます。コピー構築子を持たないなら、隠された浅い(shallow) コピーが、実行されます。
関数がいったん終わると、オブジェクト解体子が呼ばれます。


New/Delete

New と Delete は、動的にメモリを割り当て、次に、それを破壊するための、特別な演算子です。
動的メモリと共に使用されるので、それはポインタと共に使用されます。
これまで上の全ての例で、オブジェクトを作るために、Dim だけを使いました。Dim は、オブジェクトを、スタックに作成します。
しかし、New を使うと、動的にオブジェクトを作成できます。こうすると、通常のメモリで、Allocate/DeAllocate を使用するように、より多くの柔軟性を許容できます。
new についての別の重要なものは、new の後で、ポインタが NULL かどうかをチェックする必要がないことです。あなたが allocate をしたときと、同じです。
new が失敗すると、例外を引き起こします。それは、プログラムを終わらせます。
FreeBASIC の新しいバージョンでは、 より良い例外処理を許容すために、何らかの捕捉メカニズムが作られることは、ありそうです。しかし、これを書いている現在、まだ実現されていません。

new/delete には、二つの異なった種類があります。
1番目は、単一の要素かオブジェクトを、作成します。例えば:

Dim As Integer Ptr foo = New Integer

*foo = 1
Print *foo

Delete foo
Sleep


これは、新しい Integer を作成します。次に、delete が呼ばれたとき、それを破壊します。
私が ptr を使用したのを思い出してください。それが動的メモリだからです。
簡単なデータ型として、あなたは、また、デフォルト値を指定できます。デフォルト値を丸括弧で囲んで、データ型の後に置くことによって。例えば:

Dim As Integer Ptr foo = New Integer (42)

Print *foo

Delete foo
Sleep


これは、また、簡単なデータ項目がある UDT のために働きます:

Type foo
  x As Integer
  y As Integer
End Type

Dim As foo Ptr bar = New foo (1, 2)

Print bar->x, bar->y

Delete bar
Sleep


この初期化は、構築子/解体子などにかかわる、より複雑な型に、働きません。しかし、役に立つ特徴は、オブジェクトで、new/delete を使うとき、これは、構築子と解体子も呼ぶことです。
下の例を試してみてください:

Type foo
  Declare Constructor ()
  Declare Destructor ()
  x As Integer
  y As Integer
End Type

Constructor foo ()
  Print "構築子"
End Constructor

Destructor foo ()
  Print "解体子"
End Destructor

Dim As foo Ptr bar = New foo

Delete bar
Sleep


あなたは、オブジェクトのための構築子と解体子が呼ばれるのを見るでしょう。

new/delete の 2番目の型は、配列を作るものです。このとき、要素数は、データ型の後で、角括弧'[]'の中に置かれます。
配列バージョンを使うときは、'delete' の代わりに 'delete[]' を使わなければなりません。配列を削除しようとしていることを、FreeBASIC に知らせるためです。
ここにInteger型を使う、分かりやすい例があります:

Dim As Integer Ptr foo = New Integer [20]

foo[1] = 1
Print foo [1]

Delete[] foo
Sleep


これは、20 の索引要素の動的配列を作ります。
これは、Allocate と異なっていることに注意して下さい。Allocate は、引数としてバイト数を取ります。
new を使って、要素の数を指定できます。
配列メソッドは、オブジェクトのためにも、同様に働きます:

Type foo
  Declare Constructor ()
  Declare Destructor ()
  x As Integer
  y As Integer
End Type

Constructor foo ()
  Print "構築子"
End Constructor

Destructor foo ()
  Print "解体子"
End Destructor

Dim As foo Ptr bar = New foo [3]

Delete[] bar
Sleep


このコードを走らせると、3つの構築子/解体子の組が呼ばれるのを見るでしょう。私たちは、foo の3つのインスタンスの配列を作成したからです。

New と共に割り当てた全てのメモリのために、忘れずに Delete、または Delete[] を呼ばなければなりません。さもないと、メモリリークを引き起こすでしょう。
Allocate 関数で、割り当てた、メモリに対して、忘れないで DeAllocate を呼ばなければならないことと、同じです。

Name Mangling (名前修飾、名前変形、名前短縮)


名前短縮(または、名前修飾)は、下位レベルの、舞台裏で起こることなので、知っておかなければならないことではありません。
名前短縮が必要な理由は、同じ名前を共有している複数の関数に起因する問題を、解決することです。複数の関数が同じ名前を共有する状況は、関数が多重定義されているか、または関数が型の一部であるときに起こります。
下に示す、Sub の多重定義の例を、ご覧下さい:

Sub foo Overload ()

End Sub

Sub foo (ByVal i As Integer)

End Sub


名前短縮がないと、両方とも、下位レベルでは、FOO として知らされるので、FOO は、名前衝突を引き起こします。このため、FOO を使うときに、どれを呼ぶかを指定するために、装飾しなければなりません。
最初の Sub に関して、コンパイラは実際に _Z3FOOv と呼ばれる Sub を作成します。そして、2番目のために、コンパイラは、_Z3FOOi と呼ばれる Sub を作成します。
コンパイラは、次に、これらを覚えていて、あなたがそれをどう呼ぶかによって、呼ぶべき適切な Sub を選びます。例えば、 'foo()' は、実際に _Z3FOOv を呼んで、'foo(1)' は、実際に _Z3FOOi を呼びます。
ここで分かることは、'v' は void (欠けた。引数がない)を、'i' は、integer (整数) を表す、ということです。
名前短縮の一部始終は、かなり複雑で、コンパイラの間で異なります。Microsoft コンパイラは、GNU コンパイラと異なる名前短縮の枠組みを使います。そして、他のコンパイラは、また、異なった枠組みを使います。
知っておく必要があることは、FreeBASIC は、GCC 3.x ABI (アプリケーション・バイナリ・インタフェース) に従う、ということです。 これは、多重定義された関数や、複素数型は、いずれも、同じ枠組みを使う他のコンパイラと互換性があることだけを、意味しています。
これは不幸な制限ですが、これは本当に FreeBASIC 問題ではありません。そして、これは高度な機能を使うすべてのコンパイラで、一般的です。そして、すべてのコンパイラ作者が、共通の名前短縮の枠組みに同意したとしても、まだ不適合を引き起こす他の問題があります。

暗黙の this

このことも、また、普通は、知る必要は有りません。下位レベルの舞台裏で起こることだからです。
オブジェクトのメンバー関数を呼ぶときに、実際に起こることは、隠された最初のパラメタが渡されることです。それで、関数は、オブジェクトのどのインスタンスが参照されているか、を知ることができるのです。
これは、また、特性/構築子/解体子/演算子 のメンバーにとっても、同様です。
下の、非常に単純な例を、ご覧下さい:

Type foo
  Declare Sub bar (ByVal n As Integer)
  x As Integer
End Type

Sub foo.bar (ByVal n As Integer)
  x = n
End Sub

Dim baz As foo
baz.bar (5)


実際に舞台裏で起こることは、下と基本的に同じことです:

Type foo
  x As Integer
End Type

Sub foo_bar (ByRef _this As foo, ByVal n As Integer)
  _this.x = n
End Sub

Dim baz As foo
foo_bar (baz, 5)


明示的な 'this' を使う、すぐ上の方法は、この方法をより簡単にする便宜を持たない言語で、しばしば使われます。
OOP は、単に一つの概念です。OOP は、ほとんどどんな言語ででも、大部分コード化することができます。若干のものは、実装することが、難しいものも有ります。例えば構築子です。あなたは、明示的に、 'create' か 'init' 関数を呼ばなければならないでしょう。
非公開/公開の区別などのいくつかのものは、コンパイラがそれらを実施するのを知らないので、さらに難しいか、または不可能です。
OOP の機能を言語に追加する理由は、この多くを隠して、より簡単で、また、使用中に明白になるように、糖衣構文を加えることです。(糖衣構文とは、従来の構文と等価で、人間にとって読み書きしやすいように簡略化された構文のことです。)特性を、まるでそれが、関数というよりむしろ、普通のデータ・メンバーであるように、使うことができるようにする方法です。そして、それは本当にそうなのです。


デバッグ/プロフィーリングのためのヒント


GDB や他のデバッガと、gprof プロファイリング・ツールを使うとき、示される情報は、C++ の構文です。そして、あなたのすべての変数名と他のシンボルは、大文字で示されます。
下に、これらがどのように示されているかを理解するための助けとして、短い概要を列挙します:

下は、例の型です:

Type bar
  Declare Constructor ()
  Declare Constructor (ByRef obj As bar)
  Declare Constructor (ByVal n As Integer)
  Declare Destructor ()
  Declare Operator Cast() As Any Ptr
  Declare Operator Let (ByVal n As Integer)
  Declare Property foo (ByVal n As Integer)
  Declare Property foo() As Integer
  member As Any Ptr
End Type


GDB を使用するとき、これらは、下のように示されます。
(注意:C++ では、私たちが . (ドット)を使うところで、 :: を使います。'::'は、スコープ解決演算子として知られています。):

BAR::BAR() - デフォルト構築子
BAR::BAR(BAR&) - コピー構築子 (C++では、& は、byref のような「参照」を意味します)
BAR::BAR(int) - 整数の引数を取る、構築子 (注意:ByVal を示す、特別なシンボルはありません。ByVal は、C/C++ の、デフォルトの引数を渡す方法だからです。)
BAR::~BAR() - 解体子
BAR::operator void*() - Any ptrへの型変換 (void は Any と同様です。* はポインタを意味します)
BAR:: operator=(int) - 代入演算子 (Let)、'=' で示されます、C/C++ では、'='は代入です、'==' は、等値テストです。
BAR:: FOO(int) - 特性 foo の設定で、整数の引数を取ります。
BAR:: FOO() - 特性 foo の取得

sub/function のメンバーは、properties と同様に示されます。索引付きの properties も、まさしくインデックスのための、追加の引数付きで、同様に示されます。

FB データ型は、下のように示されます:

Any ptr - void *
ZString ptr - char *
String - FBSTRING
byte - signed char
ubyte - bool
short - short
ushort - unsigned short
integer - int
uinteger - unsigned int
longint - long long
ulongint - unsigned long long

この文章が、GDB/gprof で、どのように表示されるかを理解するきっかけになることを、願っています。いつも、小さな実験をしてみると、役に立ちますよ。

さらにお読みになりたい方に

演算子 New
演算子 Delete
http://en.wikipedia.org/wiki/Copy_constructor
http://en.wikipedia.org/wiki/Object_copy
http://en.wikipedia.org/wiki/Name_mangling

いっしょに学ぼう に戻る
←リンク元に戻る プログラム開発関連に戻る

最終、Sancho3 によるレビュー(2018年2月06日)

ページ歴史:2018-02-10 13:40:16
日本語翻訳:WATANABE Makoto、原文著作者:YetiFoot

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

表示-非営利-継承