2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

recordなのにメソッドチェーン?しかもFree不要なDelphiテクニック(スマートポインタ風)

2
Last updated at Posted at 2026-04-22

サンプルとして以下の機能を持つオブジェクトを設計します

「1桁の数字配列」を内部に持つシンプルなオブジェクトを扱います。
例:123 → 内部データは [1,2,3]

このオブジェクトに以下のAPIを持たせます。


■ API

  • Add(D: Integer)
    数字を追加する

  • Reverse
    中身を反転する

  • ToString
    配列を文字列として取得する

  • Concat(const Other: TDigitClass): TDigitClass
    2つのインスタンスを結合した新しいインスタンスを返す


まずは普通にクラスで書くとこうなります

type
  TDigitClass = class
  private
    FData: TArray<Integer>;
  public
    constructor Create(Value: Integer);
    procedure Add(D: Integer);
    procedure Reverse;
    function ToString: string;
    function Concat(const Other: TDigitClass): TDigitClass;
  end;

constructor TDigitClass.Create(Value: Integer);
var
  s: string;
  i: Integer;
begin
  s := Value.ToString;
  SetLength(FData, Length(s));
  for i := 1 to Length(s) do
    FData[i - 1] := Ord(s[i]) - Ord('0');
end;

procedure TDigitClass.Add(D: Integer);
var
  Len: Integer;
begin
  Len := Length(FData);
  SetLength(FData, Len + 1);
  FData[Len] := D;
end;

procedure TDigitClass.Reverse;
var
  i, Len: Integer;
  NewData: TArray<Integer>;
begin
  Len := Length(FData);
  SetLength(NewData, Len);

  for i := 0 to Len - 1 do
    NewData[i] := FData[Len - 1 - i];

  FData := NewData;
end;

function TDigitClass.ToString: string;
var
  i: Integer;
begin
  Result := '';
  for i := 0 to High(FData) do
    Result := Result + FData[i].ToString;
end;

function TDigitClass.Concat(const Other: TDigitClass): TDigitClass;
begin
  Result := TDigitClass.Create(0);
  Result.FData := Self.FData + Other.FData;
end;

使用例👇

  A := TDigitClass.Create(123);
  B := TDigitClass.Create(678);
  A.Add(4);
  A.Reverse;
  A.Add(5);
  Writeln(A.ToString);  // 出力: 43215
  
  C := A.Concat(B);
  Writeln(C.ToString);  // 出力: 43215678
  A.Free;
  B.Free;
  C.Free;

不満

  • メソッドチェーンできない
    通常、メソッドチェーンは「非破壊操作」1では実現できますが今回は破壊系2で実行したい
  • Free書かないといけない
  • 演算子使えない

やりたいこと

  A := TDigitClass.Create(123);
  B := TDigitClass.Create(678);
  Writeln(A.Add(4).Reverse.Add(5).ToString); // 出力: 43215
  Writeln((A + B).ToString); // 出力: 43215678

👉しかも Free を書きたくない


発想:スマートポインタ風にする

Delphiにはインターフェースの参照カウントがあります。

👉 最後の参照が消えたら自動でDestroyされる

これを使います。


核となる設計

record型を実装して以下のフィールドを持たせる

外部インターフェースのAPIをすべて実装し、TDigitClass をラップする

FCore: TDigitClass ; // 本体
FRef : IInterface; // 寿命管理(参照カウント)

👉 同じインスタンスを2つで持つ

  • FCore → 普通に使う
  • FRef → 生存管理

なぜInterfaceではなくrecordなのか

  • 演算子オーバーロードが使える
  • 値型っぽく扱える
  • 外部APIのインターフェースをいちいち宣言する必要なし

👉 クラスではできない


今回のテクニックを使った実装コード

program SmartRecordDemo;

{$APPTYPE CONSOLE}

uses
  System.SysUtils;

type
  //元のクラスそのものにインターフェースを実装する
  TDigitClass = class(TInterfacedObject)
  private
    FData: TArray<Integer>;
  public
    constructor Create(Value: Integer);
    procedure Add(D: Integer);
    procedure Reverse;
    function ToString: string;
    function Concat(const Other: TDigitClass): TDigitClass;
    // ログ取得用に追加
    destructor Destroy; override;
  end;

  // スマートポインタ風のレコード
  TDigit = record
  private
    FCore: TDigitClass;
    FRef : IInterface;
    function Core: TDigitClass;
  public
    constructor Create(Value: Integer);
    function Add(D: Integer): TDigit;
    function Reverse: TDigit;
    function ToString: string;
    // 演算子オーバーロードが使えるので実装する
    class operator Add(const A, B: TDigit): TDigit;
  end;

//////////////////////////
// TDigitClass
//////////////////////////
constructor TDigitClass.Create(Value: Integer);
var
  s: string;
  i: Integer;
begin
  s := Value.ToString;
  SetLength(FData, Length(s));
  for i := 1 to Length(s) do
    FData[i - 1] := Ord(s[i]) - Ord('0');
end;

procedure TDigitClass.Add(D: Integer);
var
  Len: Integer;
begin
  Len := Length(FData);
  SetLength(FData, Len + 1);
  FData[Len] := D;
end;

procedure TDigitClass.Reverse;
var
  i, Len: Integer;
  NewData: TArray<Integer>;
begin
  Len := Length(FData);
  SetLength(NewData, Len);

  for i := 0 to Len - 1 do
    NewData[i] := FData[Len - 1 - i];

  FData := NewData;
end;

function TDigitClass.ToString: string;
var
  i: Integer;
begin
  Result := '';
  for i := 0 to High(FData) do
    Result := Result + FData[i].ToString;
end;

function TDigitClass.Concat(const Other: TDigitClass): TDigitClass;
begin
  Result := TDigitClass.Create(0);
  Result.FData := Self.FData + Other.FData;
end;

// ログの出力用に追加
destructor TDigitClass.Destroy;
begin
  Writeln('>> Destroy TDigitClass');
  inherited;
end;

//////////////////////////
// TDigit
//////////////////////////
constructor TDigit.Create(Value: Integer);
begin
  FCore := TDigitClass.Create(Value);
  FRef  := FCore;
end;

function TDigit.Core: TDigitClass;
begin
  if FRef = nil then
    raise Exception.Create('TDigit is not initialized');
  Result := FCore;
end;

function TDigit.Add(D: Integer): TDigit;
begin
  Core.Add(D);
  Result := Self;
end;

function TDigit.Reverse: TDigit;
begin
  Core.Reverse;
  Result := Self;
end;

function TDigit.ToString: string;
begin
  Result := Core.ToString;
end;

class operator TDigit.Add(const A, B: TDigit): TDigit;
var
  Inst: TDigitClass;
begin
  Inst := A.Core.Concat(B.Core);
  Result.FCore := Inst;
  Result.FRef  := Inst;
end;

// デモ実行
procedure RunDemo;
var
  A, B: TDigit;
begin
  Writeln('--- デモ ---');
  A := TDigit.Create(123);
  A.Add(4).Reverse.Add(5);
  Writeln('A = ', A.ToString);

  B := TDigit.Create(678).Reverse.Add(9);
  Writeln('B = ', B.ToString);

  Writeln('A + B = ', (A + B).ToString);

  Writeln('--- 終了 ---');
  Readln;
end;
begin
  RunDemo;
  Readln;
end.

デモ結果

--- デモ ---
A = 43215
B = 8769
A + B = 432158769
--- 終了 ---

>> Destroy TDigitClass
>> Destroy TDigitClass
>> Destroy TDigitClass

何が起きているか

  • recordはコピーされる
  • でも中身は同じTDigitClassオブジェクトを共有
  • interfaceが寿命を管理

👉 最後の参照が消えたらDestroy


この手法のメリット

  • メソッドチェーンできる
    👉 通常メソッドチェーンは非破壊操作1で実現されるが、 今回は破壊系2でありながら Self を返すことで成立させているのがポイント
  • Free不要
  • 演算子も使える
  • Interface型の外部APIのインターフェースをいちいち宣言する必要なし

  1. 非破壊操作とは、元のインスタンスを変更せず、新しいインスタンスを返す処理を指します(例:Concat)。一般的には新しい値を返す関数として実装されます。 2

  2. 破壊的操作とは、インスタンス自身の状態を書き換える処理を指します(例:Add / Reverse)。一般的には副作用を持つメソッドとして実装されます。 2

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?