Help us understand the problem. What is going on with this article?

<6> Dephi のオブジェクト指向拡張 (Pascal へのオブジェクト指向拡張の歴史と Delphi)

6. Dephi のオブジェクト指向拡張

1995 年に発売された Delphi の言語は Object Pascal と呼ばれました。
image.png
Delphi のオブジェクト指向拡張を細かいトコまで説明していると大変なのでざっくりとした説明に留めます。

なお、サンプルコード中で // による行コメントを多用していますが、ご了承ください 1

See also:

6.1. Delphi のオブジェクト型

Delphi には Turbo Pascal 由来のオブジェクト型が下位互換性 2 のために残されています。非推奨でドキュメントも非常に少ないのですが、今でも使う事ができます。

ObjTest.dpr
program ObjTest;
{$APPTYPE CONSOLE}

type
  TObj = object
  private
    FValue: Integer;
  public
    constructor Init;
    destructor Done; virtual;
    function Multiply(n: Integer): Integer;
    property Value: Integer read FValue write FValue;
  end;
  PObj = ^TObj;

  { TObj }
  constructor TObj.Init;
  begin
    FValue := 1;
  end; { TObj.Init }

  destructor TObj.Done;
  begin
    ;
  end; { TObj.Done }

  function TObj.Multiply(n: Integer): Integer;
  begin
    result := FValue * n
  end; { TObj.Multiply }

var
  sObj: TObj;
  dObj: PObj;
begin
  // Static
  sObj.Init;
  sObj.Value := 3;
  Writeln(sObj.Multiply(5));

  // Dynamic
  dObj := New(PObj, Init);
  try
    dObj^.Value := 7;
    Writeln(dObj^.Multiply(9));
  finally
    Dispose(dObj, Done);
  end;

  Readln;
end. { Main }

Delphi では、ポインタの逆参照を自動的に想定する参照モデルを使うため、動的なオブジェクトを指すための逆参照演算子 (^) を省略する事ができます。上記コードだと、メインブロック中の ^ を取り除いても動作します 3。定義部の ^TObj の前にある ^ は逆参照演算子ではないので省略できません。

See also:

6.2. Delphi のクラス型

クラス という用語は Delphi 1 において、次のように定義されています。

データと関連コードを表す機能を一つの単位に集めたリスト。クラスには定義にある機能だけではなく、上位オブジェクトから継承した機能も含まれます。「クラス」という用語は「オブジェクト型」と同義です。

Turbo Pascal では クラス という用語を使っていなかったので混乱しそうになりますが、Delphi ではオブジェクト型もクラス型も (広義の) クラス型です。クラス型はレコード型と同様、構造化型の一種です。
image.png
クラス型は型宣言部 (type) にて "型名 = class (親クラス) ~ end;" として定義します。親クラスは省略できますが、省略した場合にはルートクラスである TObject を継承します。

クラスには次に挙げるようなメンバーがあります。

フィールド

フィールドはオブジェクトに属する変数のようなものです。他言語での "メンバ変数" や "インスタンス変数" に相当します。通常は可視性を private で定義します。

なお、クラスのフィールドを "クラスフィールド" とか "クラス変数" と呼んではいけません。そちらには別の意味があります。

メソッド

メソッドとはクラス型宣言内で宣言された、オブジェクトに対して操作を行う手続き (procedure) または関数 (function) を指します。他言語の "メンバ関数" に相当します。

コンストラクタとデストラクタはオブジェクトの作成と廃棄を制御する特殊なメソッドです。

なお、クラスのメソッドを "クラスメソッド" とか "クラス関数" と呼んではいけません。そちらには別の意味があります。

プロパティ

プロパティはオブジェクトの属性を定義するものです。フィールドの値を読み書きするメソッド (ゲッター/セッター) をカプセル化したりできます。プロパティの例には、フォームのキャプション、フォントのサイズ、データベーステーブルの名前などがあります。

See also:

6.2.1. クラス型の定義

次のようにしてクラスを定義できます。

type
  TCls = class(TObject) { TObject を継承したクラス型 TCls の定義 }
  private
    FValue: Integer;
  end;
  • ルートクラスは TObject です。
  • 親クラスを指定しなかった場合の親クラスは TObject です。
  • 多重継承はできません。
  • 可視性指定子があります。
  • プロパティがあります。
  • クラスメソッドがあります。
  • 静的なオブジェクトは作れません。

6.2.2. クラス型の定義

クラスの作成と破棄は次のようにして行います。次のコードは ObjTest.dpr のクラス型版です。

ClsTest.dpr
program ClsTest;
{$APPTYPE CONSOLE}

type
  TCls = class
  private
    FValue: Integer;
  public
    constructor Create;
    destructor Destroy; override;
    function Multiply(n: Integer): Integer;
    property Value: Integer read FValue write FValue;
  end;

  { TCls }
  constructor TCls.Create;
  begin
    FValue := 1;
  end; { TCls.Create }

  destructor TCls.Destroy;
  begin
    inherited;
  end; { TCls.Destroy }

  function TCls.Multiply(n: Integer): Integer;
  begin
    result := FValue * n
  end; { TCls.Multiply }

var
  Cls: TCls;
begin
  Cls := TCls.Create;
  try
    Cls.Value := 7;
    Writeln(Cls.Multiply(9));
  finally
    Cls.Free;
  end;

  Readln;
end. { Main }

コンストラクタ Create() と Free() メソッドですね。自動参照カウント (ARC) が有効な場合には DisposeOf() メソッドを使って破棄します。

クラスではクラス型とポインタ型を別々に宣言したり、オブジェクトポインタを明示的に逆参照する必要はありません。

See also:

6.2.3. 可視性指定子 (Visibility Specifiers)

Delphi の可視性指定子とその範囲は次の通りです。

同一ユニット 別ユニット 下位クラス 実行時
型情報
オートメーション
型情報
strict private 1 × × × × ×
private × × × ×
strict protected 1 × × × ×
protected × × ×
public × ×
published ×
automated (Win32) ×

published を使うには {$M+} コンパイラ指令を付ける必要があります。また、published に指定できるフィールドはクラス型またはインターフェイス型に限定されます。

Unit1.pas
unit Unit1;

interface

type
  TValue = class
  private
    FValue: Integer;
  public
    property Value: Integer read FValue write FValue;
  end;

  TClsA = class
    procedure Dummy; virtual;
  strict private
    FStrictPrivate: TValue;
  private
    FPrivate: TValue;
  strict protected
    FStrictProtected: TValue;
  protected
    FProtected: TValue;
  public
    FPublic: TValue;
  published
    FPublished: TValue;
  end;

  TClsB = class(TClsA)
    procedure Dummy; override;
  end;

  procedure DummyU1;

implementation

procedure DummyU1;
var
  V: Integer;
  ClsA: TClsA;
  ClsB: TClsB;
begin
  // --------------------------------------
  // Unit1.TClsA
  // Unit1.ClsA <- アクセス
  // --------------------------------------
  ClsA := TClsA.Create;
  try
//  V := ClsA.FStrictPrivate.Value;   // NG
    V := ClsA.FPrivate.Value;
//  V := ClsA.FStrictProtected.Value; // NG
    V := ClsA.FProtected.Value;
    V := ClsA.FPublic.Value;
    V := ClsA.FPublished.Value;
  finally
    ClsA.Free;
  end;

  // --------------------------------------
  // Unit1.TClsA, Unit1.TClsB(継承)
  // Unit1.ClsB <- アクセス
  // --------------------------------------
  ClsB := TClsB.Create;
  try
//  V := ClsB.FStrictPrivate.Value;   // NG
    V := ClsB.FPrivate.Value;
//  V := ClsB.FStrictProtected.Value; // NG
    V := ClsB.FProtected.Value;
    V := ClsB.FPublic.Value;
    V := ClsB.FPublished.Value;
  finally
    ClsB.Free;
  end;
end; { Dummy }

{ TClsA }

procedure TClsA.Dummy;
var
  V: Integer;
begin
// --------------------------------------
// Unit1.TClsA
// Unit1.TClsA.Self <- アクセス
// --------------------------------------
  V := Self.FStrictPrivate.Value;
  V := Self.FPrivate.Value;
  V := Self.FStrictProtected.Value;
  V := Self.FProtected.Value;
  V := Self.FPublic.Value;
  V := Self.FPublished.Value;
end;

{ TClsB }

procedure TClsB.Dummy;
var
  V: Integer;
begin
// --------------------------------------
// Unit1.TClsA, Unit1.TClsB(継承)
// Unit1.TClsB.Self <- アクセス
// --------------------------------------
//V := Self.FStrictPrivate.Value;
  V := Self.FPrivate.Value;
  V := Self.FStrictProtected.Value;
  V := Self.FProtected.Value;
  V := Self.FPublic.Value;
  V := Self.FPublished.Value;
end;

end.
Unit2.pas
unit Unit2;

interface

uses
  Unit1;

type
  TClsC = class(TClsA)
    procedure Dummy; override;
  end;

  procedure DummyU2;

implementation

procedure DummyU2;
var
  V: Integer;
  ClsA: TClsA;
  ClsB: TClsB;
  ClsC: TClsC;
begin
  // --------------------------------------
  // Unit1.TClsA
  // Unit2.ClsA <- アクセス
  // --------------------------------------
  ClsA := TClsA.Create;
  try
//  V := ClsA.FStrictPrivate.Value;   // NG
//  V := ClsA.FPrivate.Value;         // NG
//  V := ClsA.FStrictProtected.Value; // NG
//  V := ClsA.FProtected.Value;       // NG
    V := ClsA.FPublic.Value;
    V := ClsA.FPublished.Value;
  finally
    ClsA.Free;
  end;

  // --------------------------------------
  // Unit1.TClsA, Unit1.TClsB
  // Unit2.ClsB <- アクセス
  // --------------------------------------
  ClsB := TClsB.Create;
  try
//  V := ClsB.FStrictPrivate.Value;   // NG
//  V := ClsB.FPrivate.Value;         // NG
//  V := ClsB.FStrictProtected.Value; // NG
//  V := ClsB.FProtected.Value;       // NG
    V := ClsB.FPublic.Value;
    V := ClsB.FPublished.Value;
  finally
    ClsB.Free;
  end;

  // --------------------------------------
  // Unit1.TClsA, Unit2.TClsC
  // Unit2.ClsC <- アクセス
  // --------------------------------------
  ClsC := TClsC.Create;
  try
//  V := ClsC.FStrictPrivate.Value;   // NG
//  V := ClsC.FPrivate.Value;         // NG
//  V := ClsC.FStrictProtected.Value; // NG
    V := ClsC.FProtected.Value;
    V := ClsC.FPublic.Value;
    V := ClsC.FPublished.Value;
  finally
    ClsC.Free;
  end;

end; { DummyU2 }

{ TClsC }
procedure TClsC.Dummy;
var
  V: Integer;
begin
// --------------------------------------
// Unit1.TClsA, Unit2.TClsC(継承)
// Unit2.TClsC.Self <- アクセス
// --------------------------------------
//V := Self.FStrictPrivate.Value;   // NG
//V := Self.FPrivate.Value;         // NG
  V := Self.FStrictProtected.Value;
  V := Self.FProtected.Value;
  V := Self.FPublic.Value;
  V := Self.FPublished.Value;
end;

end.

6.3. Delphi のクラス参照型 (メタクラス)

クラスのインスタンス (オブジェクト) へのポインタはクラス型の変数です。クラス参照型 (メタクラス) 4 はクラス型そのものに対するポインタです。

クラス参照型は型宣言部 (type) にて "型名 = class of 対象クラス型;" として定義します。次のようにしてクラス参照型を定義できます。

type
  TObjectClass = class of TObject;

考え方はポインタ型と同じです。

type
  TMyClass = class
    function FuncA: Integer;
    class function FuncB: Integer;
  end;
  MyC = class of TMyClass; // TMyClass のクラス参照型

  function TMyClass.FuncA: Integer;
  begin
    ...
  end;

  class function TMyClass.FuncB: Integer;
  begin
    ...
  end;

var
  MyClass: TMyClass;
  v: Integer;
begin
  MyClass := TMyClass.Create; // <- クラス参照でコンストラクタが呼び出されている
  v := MyClass.FuncA;         // オブジェクト参照
  v := TMyClass.FuncB;        // クラス参照
  v := MyC.FuncB;             // クラス参照型を使ったクラス参照
  ...

MyClass は TMyClass のオブジェクトです。正確には TMyClass のインスタンスへのポインタです。 FuncA はオブジェクト参照で呼び出されています。コード中の FuncB は TMyClass のクラスメソッドなのでクラス参照で呼び出されています。

クラスメソッドはクラス参照だけでなく、オブジェクト参照でも呼び出す事ができます。

コンストラクタは通常、クラス参照で呼び出されます。コンストラクタをオブジェクト参照で呼び出した場合、オブジェクト (インスタンス) は生成されず、通常のメソッドとして実行されます。

See also:

6.4. Delphi のインターフェイス型

(オブジェクト)インターフェイスはクラスに実装させることができるメソッドを定義します。インターフェイスはクラスと同じように宣言しますが、直接インスタンス化することはできません。

インターフェイス型は Delphi 3 で実装され、Delphi 6 で汎用的な IInterface (旧 IUnknown) が使えるようになりました。また、インターフェイスを使うと多重継承に似た事ができます。インターフェイス型もまた構造化型です。

See also:

6.4.1. インターフェイス型の定義

インターフェイス型は型宣言部 (type) にて "型名 = interface (親インターフェイス) ['GUID'] ~ end;" として定義します。

次のようにしてインターフェイス型を定義できます。GUID は Delphi のコードエディタで〔Ctrl〕+〔Shift〕+〔G〕で生成できます。インターフェイス型の親インターフェイスを省略するとルートインターフェイス IInterface から派生します。

type
  IItfA = interface
  ['{xxxxxxxx-6953-4C6B-B1E3-DBB2E7419162}']
    function FuncA: string;
  end;

  IItfB = interface(IItfA)
  ['{xxxxxxxx-261D-4742-BC7E-8D05B22D8016}']
    function FuncB: string;
  end;

6.4.2. インターフェイス型を使ったクラス

インターフェイスを使ったクラスは基本的に TInterfacedObject (またはその下位クラス) から派生します。IItfB を使ったクラスの実装例を次に示します。

type
  TCls = class (TInterfacedObject, IItfB)
    function FuncA: string;
    function FuncB: string;
  end;

  function TCls.FuncA: string;
  begin
    result := 'FuncA';
  end;

  function TCls.FuncB: string;
  begin
    result := 'FuncB';
  end;

このクラスを使ってみます。

var
  Cls: TCls;
begin
  Cls := TCls.Create;
  try
    Writeln(Cls.FuncA);
    Writeln(Cls.FuncB);
  finally
    Cls.Free;
  end;
  Readln;
end.

実行結果は次の通りです。

FuncA
FuncB

クラス型変数ではなくインターフェイス型変数を使うと参照カウントが行われるようになり、Free での破棄が不要になります。

var
  Cls: IItfB; // <- インターフェイス型変数を使う
begin
  ReportMemoryReakOnShutdown := True; // アプリケーション終了時にメモリリークを報告する
  Cls := TCls.Create;
  Writeln(Cls.FuncA);
  Writeln(Cls.FuncB);
  Readln;
end.

See also:

6.5. Delphi の抽象クラス型

Delphi 2005 以降では次のようにして抽象クラス型を定義できます。

type
  TCls = class abstract (TObject) { TObject を継承した抽象クラス型 TCls の定義 }
  end;

...なのですが、何故か抽象クラス型をインスタンス化できてしまい、エラーも警告も出ません。つまり abstract の指定はマーカーとしての意味しかありません。

abstract を指定していなくても抽象メソッドが含まれていれば抽象クラスになります。こちらもインスタンス化できてしまうのですが、コンパイラが警告 W1020 を吐きます。

次のようなコンパイラ指令を書いておけば警告をエラーに昇格できるため、抽象メソッドを含むクラスのインスタンス化は阻止する事ができます。

{$WARN CONSTRUCTING_ABSTRACT ERROR}

なお、abstract の代わりに sealed を指定すると継承できないクラス (シールドクラス/ファイナルクラス) を作る事ができます。こちらの方は継承しようとするとちゃんとエラーになります...謎です。

6.6. Delphi の高度なレコード型 (Advanced Record)

Delphi 2006 からレコード型にメソッドやプロパティ、コンストラクタを持たせられるようになりました。次のコードは ObjTest.dpr の高度なレコード型版です。

RecTest.dpr
program RecTest;
{$APPTYPE CONSOLE}

type
  TRec = record
  private
    FValue: Integer;
  public
    constructor Create(InitialValue: Integer);
    procedure Destroy;
    function Multiply(n: Integer): Integer;
    property Value: Integer read FValue write FValue;
  end;
  PRec = ^TRec;

  { TRec }
  constructor TRec.Create(InitialValue: Integer);
  begin
    FValue := InitialValue;
  end; { TRec.Init }

  procedure TRec.Destroy;
  begin
    ;
  end; { TRec.Destroy }

  function TRec.Multiply(n: Integer): Integer;
  begin
    result := FValue * n
  end; { TRec.Multiply }

var
  sRec: TRec;
  dRec: PRec;
begin
  // Static
  sRec.Value := 3;
  Writeln(sRec.Multiply(5));

  // Dynamic
  dRec := New(PRec, Create(0));
  try
    dRec^.Value := 7;
    Writeln(dRec^.Multiply(9));
    dRec^.Destroy;
  finally
    Dispose(dRec);
  end;

  Readln;
end. { Main }

オブジェクト型とほぼ同じような使い方ができるようになっています。メインブロック中の ^ を取り除いても動作します 3。Destroy() がデストラクタではなくただのメソッドになっている事に注意してください。

  • コンストラクタには最低でも一つのパラメータが必要で、初期値を持つパラメータは初期値が指定されない可能性がある (内部で使っている引数なしのコンストラクタとカブる) ため、使用する事ができません。
  • デストラクタはありません。
  • レコードなので継承はできません。
  • レコードなので可変部は普通に使えます。

See also:

6.7. Delphi のヘルパー

Delphi にはヘルパーと呼ばれる機能があります。オブジェクトを継承せずに機能を拡張するもので、大別すると次の 3 種類があります。

  • クラスヘルパー
  • レコードヘルパー
  • レコードヘルパー (組み込み型用)

クラスヘルパーとレコードヘルパーは Delphi 2006 から利用可能でしたが、真価を発揮するのは Delphi 2009 以降です。XE4 以降では Integer 等のオブジェクトではない組み込み型に対してもヘルパーを作用させる事ができるようになっています。

See also:

6.8. Delphi のレコード型、オブジェクト型、クラス型、インターフェイス型の主な違い

Delphi でのレコード型、オブジェクト型、クラス型の主な違いは次の通りです。

レコード型 レコード型
(高度)
オブジェクト型 クラス型 インターフェイス型
定義 record record object class interface
継承 × ×
フィールド ×
可変部 × × ×
メソッド ×
プロパティ × 5
可視性指定子 × 6 6 ×
コンストラクタ × 7 ×
デストラクタ × × ×

参考文献

OBJECT PASCAL HANDBOOK

image.png

タイトル 著者 ISBN-10
(Amazon)
出版年
OBJECT PASCAL HANDBOOK マルコ・カントゥ (著)
藤井 等 (訳)
487783401X 2016/6/10

最近の Delphi の文法はこれと DocWiki があればいいかと。英語版でよければ無償 DL できます。

Object Pascal Language Guide

image.png
Delphi 1.0 で加えられた変更を知る方法ですが、Delphi 2~4 の CD-ROM を持っているのなら DELPHI16\EXTRAS\MANUALS\ にある "Object Pascal Language Guide (OBJLANG.PDF)" を参照してみてください。紙マニュアルやヘルプ (WinHelp) に書かれている事と同じですが、視認性はいいと思います。

See also:

[ ← 5. (標準) Pascal へのオブジェクト指向拡張 ] [ ↑ 目次へ ]


  1. Delphi 1 では行コメントが使えません。行コメントが使えるのは Delphi 2 以降です。 

  2. 「下位互換性のために残してある」と言いながらも Delphi, Delphi 2 で拡張が行われており、例えば下にある ObjTest.dpr はプロパティが使われているために Delphi 1 ではコンパイルできません。 

  3. Delphi 2 以降で(動的)オブジェクト型や(動的)レコード型の逆参照演算子が省略できます。 

  4. Delphi 1 では オブジェクト参照型 と呼ばれていました。とても紛らわしいネーミングです。 

  5. Delphi 2 以降でプロパティが使えます。 

  6. Publihed はありません。 

  7. 1 つ以上のパラメータを持つ必要があります。 

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした