クラスとオブジェクト(Delphi)
クラスとオブジェクト:インデックス への移動
このトピックでは、次の内容を取り扱います:
- クラスの宣言構文
- 継承とスコープ
- クラス メンバの可視性
- 前方宣言と相互に依存するクラス
目次
クラス型
クラス(クラス型)は、フィールド、メソッド、プロパティから成る構造を定義したものです。クラス型のインスタンスをオブジェクトと呼びます。クラスのフィールド、メソッド、プロパティを、クラスのコンポーネントまたはメンバと呼びます。
- フィールドは、基本的に、オブジェクトの構成要素である変数です。レコードのフィールドと同様に、クラスのフィールドは、クラスの各インスタンスに存在するデータ項目を表します。
- メソッドは、クラスに関連付けられている手続きまたは関数です。大半のメソッドは、オブジェクト、つまりクラスのインスタンスに作用します。中には、クラス型そのものに作用するメソッドもあります(これをクラス メソッドと呼びます)。
- プロパティは、オブジェクトに関連付けられているデータ(多くの場合、フィールドに格納されています)に対するインターフェイスです。プロパティにはアクセス指定子があり、それらによって、データがどのように読み取られ変更されるかが決まります。オブジェクトそのものを除くプログラムの他の部分から見れば、プロパティはあらゆる点でフィールドと同じです。
オブジェクトは、動的に割り当てられたメモリ ブロックであり、その構造はオブジェクトのクラス型によって決まります。各オブジェクトには、クラスに定義されているすべてのフィールドの一意なコピーがありますが、メソッドに関しては、クラスのすべてのインスタンスで同じものを共有します。オブジェクトは、コンストラクタおよびデストラクタと呼ばれる特別なメソッドによって生成および破棄されます。
クラス型の変数は、実際には、オブジェクトを参照するポインタです。 そのため、複数の変数が同じオブジェクトを参照することが可能です。 他のポインタと同様に、クラス型の変数も nil 値を持つことができます。 ただし、クラス型の変数が指しているオブジェクトにアクセスするために、その変数を明示的に逆参照する必要はありません。 たとえば、, SomeObject.Size := 100
assigns the value 100 to the Size
property of the object referenced by SomeObject
; you would not write this as SomeObject^.Size := 100
.
クラス型をインスタンス化するには、まずそのクラス型を宣言して名前を付ける必要があります。変数宣言の中ではクラス型を定義できません。クラスの宣言は、手続きや関数の宣言内ではなく、プログラムやユニットの一番外側のスコープでのみ行います。
クラス型宣言の形式は以下のとおりです。
type className = class [abstract | sealed] (ancestorClass) memberList end;
ここで、className は任意の有効な識別子です。sealed キーワードまたは abstract キーワードは任意指定で、"(ancestorClass)" も任意指定です。memberList ではクラスのメンバ(つまり、フィールド、メソッド、プロパティ)を宣言します。 [abstract | sealed]
の構文(角かっこ [ ]
とその中のパイプ |
)は、任意指定のキーワード sealed と abstract のどちらか一方のみ使用できることを指定するのに用いられます。 意味を持つのは sealed キーワードまたは abstract キーワードのみであり、角かっこ記号とパイプ記号そのものは削除する必要があります。
(ancestorClass) を省略した場合、新しいクラスは定義済みの System.TObject クラスを直接継承します。(ancestorClass) を指定し、かつ memberList が空の場合は、end を省略できます。クラス型宣言には、そのクラスで実装されるインターフェイスのリストを含めることもできます(「インターフェイスの実装」を参照)。
クラスが sealed と指定されている場合は、そのクラスを継承によって拡張することはできません。
たとえ抽象仮想メソッドがまったく含まれていなくても、クラス全体を abstract と宣言することができます。
メモ: Delphi では、下位互換性を保つために、abstract と宣言されたクラスをインスタンス化することはできますが、その機能はもう使用しないでください。
クラスに abstract と sealed の両方を指定することはできません。
メソッドは、クラス宣言の中で、本体を含まない関数または手続きのヘッダーとして記述されます。各メソッドの定義宣言は、プログラムの他の場所に記述します。
例として、Classes
ユニットの TMemoryStream
クラスの宣言を次に示します。
TMemoryStream = class(TCustomMemoryStream)
private
FCapacity: Longint;
procedure SetCapacity(NewCapacity: Longint);
protected
function Realloc(var NewCapacity: Longint): Pointer; virtual;
property Capacity: Longint read FCapacity write SetCapacity;
public
destructor Destroy; override;
procedure Clear;
procedure LoadFromStream(Stream: TStream);
procedure LoadFromFile(const FileName: string);
procedure SetSize(const NewSize: Int64); override;
procedure SetSize(NewSize: Longint); override;
function Write(const Buffer; Count: Longint): Longint; override;
function Write(const Buffer: TBytes; Offset, Count: Longint): Longint; override;
end; // deprecated 'Use TBytesStream';
Classes.TMemoryStream
は Classes.TCustomMemoryStream
の下位クラスで、そのメンバはほとんどが継承されたものです。 ただしこのクラスでは、デストラクタ メソッドの Destroy
など、いくつかのメソッドやプロパティを定義(または再定義)しています。 コンストラクタの Create
は、System.TObject
のものを変更なしに継承するため、改めて宣言されていません。 各メンバは、private、protected、または public として宣言されています(このクラスに published のメンバはありません)。 これらの用語については後で説明します。
このように宣言されている場合、次のようにして TMemoryStream
のインスタンスを作成できます。
var
stream: TMemoryStream;
begin
stream := TMemoryStream.Create;
継承とスコープ
クラスを宣言するときには、その直接の上位クラスを指定することができます。次の例を見てください。
type TSomeControl = class(TControl);
ここでは、Vcl.Controls.TControl から派生した、TSomeControl というクラスを宣言しています。クラス型は、直接の上位クラスからすべてのメンバを自動的に継承します。各クラスでは、新しいメンバを宣言したり継承したメンバを再定義することができますが、上位クラスで定義されたメンバを削除することはできません。TSomeControl には、Vcl.Controls.TControl と、Vcl.Controls.TControl の上位クラスそれぞれで定義された、すべてのメンバが含まれています。
メンバの識別子のスコープは、メンバが宣言された場所から始まってクラス宣言の末尾まで続き、さらに、そのクラスのすべての下位クラスや、クラスおよび下位クラスで定義されたすべてのメソッドのブロックにまで及びます。
TObject と TClass
System ユニットで宣言されている System.TObject クラスは、他のすべてのクラスの親となる最上位のクラスです。System.TObject では、基本のコンストラクタとデストラクタなど、ごくわずかなメソッドしか定義されていません。System ユニットでは、System.TObject の他に、クラス参照型の System.TClass が次のように宣言されています。
TClass = class of TObject;
クラス型の宣言で上位クラスを指定しなければ、そのクラスは System.TObject を直接継承します。そのため、次の宣言は
type TMyClass = class ... end;
次の宣言と同じ意味になります。
type TMyClass = class(TObject) ... end;
読みやすさの点から、後者の形式をお勧めします。
クラス型の互換性
クラス型は、その上位クラス型と代入互換性があります。そのため、クラス型の変数を使って、任意の下位クラス型のインスタンスを参照することができます。たとえば、次のように宣言されているとします。
type
TFigure = class(TObject);
TRectangle = class(TFigure);
TSquare = class(TRectangle);
var
Fig: TFigure;
この Fig という変数には、TFigure 型、TRectangle 型、および TSquare 型の値を代入できます。
オブジェクト型
Delphi コンパイラでは、クラス型に別の構文を使用することができます。 次の構文を使ってオブジェクト型を宣言できます。
type objectTypeName = object (ancestorObjectType) memberList end;
objectTypeName は有効な識別子であり、(ancestorObjectType) は任意指定で、memberList ではフィールド、メソッド、プロパティを宣言します。(ancestorObjectType) を省略した場合には、親を持たない新しい型が作成されます。オブジェクト型は published メンバを持つことができません。
オブジェクト型は System.TObject を継承しないため、組み込みのコンストラクタやデストラクタやその他のメソッドが提供されません。オブジェクト型のインスタンスは、New 手続きで作成して Dispose 手続きで破棄することができます。あるいは、レコードの場合と同様に、オブジェクト型の変数をそのまま宣言することも可能です。
オブジェクト型は下位互換性のためにサポートされているにすぎません。 これらの使用は推奨されません。
クラス メンバの可視性
クラスの各メンバには可視性という属性があり、これは private、protected、public、published、または automated のいずれか 1 つの予約語で示されます。次の例を見てください。
published property Color: TColor read GetColor write SetColor;
ここでは、Color という published のプロパティを宣言しています。可視性によって、メンバにどこからどのようにアクセスできるかが決まります。最も厳しいアクセス制限を表すのが private、中間のアクセス制限が protected、アクセスできる範囲が最も広いのが public、published、automated です。
メンバの宣言に可視性指定子が付けられていない場合には、メンバの可視性はその前のメンバと同じになります。クラス宣言の先頭にあるメンバに可視性が指定されていない場合は、そのクラスが {$M+} の状態でコンパイルされたか {$M+} の状態でコンパイルされたクラスから派生しているなら、メンバの可視性はデフォルトの published になり、それ以外であれば public になります。
コードを読みやすくするために、クラス宣言は可視性ごとに整理するとよいでしょう。まず private メンバをすべてまとめて配置し、その後に protected メンバをすべて置くといった構成です。こうすれば、可視性を示す予約語はそれぞれ一度だけ記述され(記述されないこともあります)、宣言の新しい 'セクション' の先頭を示すようになります。その結果、典型的なクラス宣言は次のようになります。
type
TMyClass = class(TControl)
private
{ private declarations here }
protected
{ protected declarations here }
public
{ public declarations here }
published
{ published declarations here }
end;
下位クラスでプロパティを再宣言することで、可視性を広げることができますが、可視性を狭めることはできません。 たとえば、protected のプロパティを下位クラスで public にすることは可能ですが、private にはできません。 また、published のプロパティを下位クラスで public にすることはできません。 詳細は、「プロパティのオーバーライドと再宣言」を参照してください。
private、protected、public のメンバ
private のメンバは、そのメンバのクラスが宣言されているユニットまたはプログラムの外からは見えません。つまり、private のメソッドを別のモジュールから呼び出したり、private のフィールドやプロパティを別のモジュールから読み書きすることはできません。関連するクラス宣言を同じモジュールにまとめると、メンバのアクセス可能性を広げなくても、各クラスから別のクラスの private のメンバにアクセスできるようになります。メンバがその宣言元クラスの中でのみ参照できるようにするには、メンバを strict private と宣言する必要があります。
protected のメンバは、クラスが宣言されているモジュール内の任意の場所と、任意の下位クラスから(下位クラスがどのモジュールに含まれているかに関係なく)見ることができます。protected メンバが宣言されているクラスの下位クラスに属する任意のメソッド定義の中から、protected のメソッドを呼び出したり、protected のフィールドやプロパティを読み書きすることができます。派生クラスの実装でのみ使う予定のメンバは、通常は protected にします。
public のメンバは、そのクラスを参照できる場所であればどこからでも見ることができます。
strict 可視性指定子
Delphi コンパイラでは、private および protected の可視性指定子の他に、アクセス制約の厳しい別の可視性設定をサポートしています。 これは、strict private および strict protected の可視性です。
可視性が strict private のクラス メンバには、そのメンバが宣言されたクラスの中からしかアクセスできません。 同じユニット内で宣言された手続きや関数からは見えません。 可視性が strict protected のクラス メンバには、そのメンバが宣言されたクラスと、(宣言されている場所に関係なく)その下位クラスの中からしかアクセスできません。 さらに、インスタンス メンバ(class または class var というキーワードを付けずに宣言されたメンバ)を strict private または strict protected と宣言すると、メンバが含まれるクラスのそのインスタンスの外からアクセスできなくなります。 クラスのインスタンスは、同じクラスであっても別のインスタンスの strict private または strict protected のインスタンス メンバにはアクセスできません。
メモ: strict という語は、クラス宣言のコンテキストでは指令として扱われます。クラス宣言の中で 'strict' という名前のメンバを宣言することはできませんが、クラス宣言外では使用可能です。
published メンバ
published メンバの可視性は public メンバと同じです。異なるのは、published メンバについては実行時型情報(RTTI)が生成されるという点です。RTTI を使用すると、アプリケーションでオブジェクトのフィールドやプロパティを動的に問い合わせたり、メソッドを探すことができます。RTTI は、フォーム ファイルの保存および読み込み時にプロパティの値にアクセスする、[オブジェクト インスペクタ]にプロパティを表示する、特定のメソッド(イベント ハンドラと呼ばれるもの)を特定のプロパティ(イベントと呼ばれるもの)に関連付けるといった用途で使われます。
published にできるプロパティは特定のデータ型に限られます。順序型、文字列、クラス、インターフェイス、バリアント、メソッド ポインタ型を published にすることができます。また、基底型の上限および下限が 0 ~ 31 の順序値であれば、集合型も published にすることができます (集合がバイト、ワード、またはダブルワードに収まらなければならないということです)。Real48 以外の実数型は published にすることができます。配列型のプロパティ(配列プロパティとは異なります。これについては次に説明します)は published にできません。
published を指定できるプロパティの中にも、ストリーミング システムで完全にサポートされていないものがあります。 レコード型のプロパティ、published を指定できるすべての型の配列プロパティ、無名値を含む列挙型のプロパティなどがそれに該当します。 この種のプロパティを published にすると、そのプロパティは[オブジェクト インスペクタ]に正しく表示されず、オブジェクトをディスクにストリーミングするときにプロパティの値が維持されません。
メソッドはすべて published にできますが、1 つのクラスで同じ名前を持つ複数のオーバーロード メソッドに published を指定することはできません。フィールドを published にできるのは、そのフィールドがクラス型またはインターフェイス型である場合だけです。
クラスは、{$M+} 状態でコンパイルされたか、{$M+} 状態でコンパイルされたクラスの下位クラスでなければ、published のメンバを持つことができません。published のメンバを持つクラスのほとんどは、{$M+} 状態でコンパイルされた Classes.TPersistent から派生しているため、$M 指令が必要になることはほとんどありません。
メモ: Unicode 文字を含む識別子は、クラスの published セクションや、published メンバで使われる型の中では使用できません。
atomated メンバ(Win32 のみ)
atomated メンバの可視性は public メンバと同じです。異なるのは、automated メンバについてはオートメーション型情報(オートメーション サーバーで必要)が生成されるという点です。automated メンバが使われるのは、通常は Win32 クラス内だけです。automated という予約語は、下位互換性のために残されています。ComObj ユニットの TAutoObject クラスでは automated を使用していません。
automated と宣言されたメソッドやプロパティには、以下の制約があります。
- プロパティ、配列プロパティ パラメータ、メソッド パラメータ、および関数の結果の型はすべて、automated を指定できるものでなければなりません。automated を指定できる型は、Byte、Currency、Real、Double、Longint、Integer、Single、Smallint、AnsiString、WideString、TDateTime、Variant、OleVariant、WordBool と、すべてのインターフェイス型です。
- メソッド宣言は、デフォルトの register 呼び出し規約に従う必要があります。virtual にはできますが、dynamic にはできません。
- プロパティの宣言にはアクセス指定子(read および write)を含めることができますが、他の指定子(index、stored、default、nodefault)は使用できません。アクセス指定子の後には、デフォルトの register 呼び出し規約を使ったメソッド識別子を記述しなければなりません。フィールド識別子は記述できません。
- プロパティの宣言には型を指定する必要があります。プロパティのオーバーライドはできません。
automated のメソッドやプロパティの宣言には、dispid 指令を含めることができます。既に使われている ID を dispid 指令で指定すると、エラーが発生します。
Win32 プラットフォームでは、この指令の後に、メンバのオートメーション ディスパッチ ID を指定する整数定数を記述しなければなりません。 記述しなければ、そのクラスおよび上位クラスのメソッドまたはプロパティで使われているディスパッチ ID のうち最大のものよりも 1 だけ大きいディスパッチ ID が、コンパイラによって自動的にメンバに割り当てられます。 オートメーション(Win32 のみ)の詳細は、「オートメーション オブジェクト」を参照してください。
前方宣言と相互に依存するクラス
クラス型の宣言が class という語とセミコロンで終わっている場合、つまり、
type className = class;
このような形式で、class という語の後に上位クラスもクラス メンバも記述されていない場合には、その宣言は前方宣言になります。前方宣言は、同じ型宣言セクションの中で同じクラスの定義宣言によって解決する必要があります。つまり、前方宣言とその定義宣言の間には、他の型宣言しか含められません。
前方宣言を使用することで、相互に依存したクラスを使用できるようになります。次はその例です。
type
TFigure = class; // forward declaration
TDrawing = class
Figure: TFigure;
// ...
end;
TFigure = class // defining declaration
Drawing: TDrawing;
// ...
end;
System.TObject から派生し、クラス メンバをまったく宣言していない型の完全な宣言と、前方宣言とを混同しないよう注意してください。
type
TFirstClass = class; // this is a forward declaration
TSecondClass = class // this is a complete class declaration
end; //
TThirdClass = class(TObject); // this is a complete class declaration