implementsを広める会

この文章は以前「ObjectPascal Object Oriented Programming Vol.1 "implements" 第2版」と大上段な名前を付けて公開していたものに、簡易化と追記を行ったものです。

今更多重継承?

そもそもオブジェクト指向の意義すら疑われる昨今、実装込みの多重継承(C++, Eiffel*1)はオブジェクト指向言語の主流からもすっかり遠のいて、型のみの多重継承、つまりインターフェース継承(Ada, C#, D, Java, ObjectPascal(Delphi))やDuck-Typing(Google Go, VB.NET)が主流となってきました。
そもそもC++の多重継承は問題が多く、Eiffelの多重継承は衝突を上手く捌けてC++のような問題はないものの、他の言語ではモジュールシステム(パッケージ、名前空間)で解決している部分までガンガン多重継承してしまう乱用っぷりは、やっぱりちょっと引きます。……感覚的な話でごめんなさい。

委譲と委任

そんな多重継承ですが、インターフェース継承を用いて実装継承と同じことをしようと思えば……やっぱりどうしてもこういうことをする場面は出てきます……インターフェースのメソッドをひとつひとつ、実装へ投げるコードが必要になります。所謂委譲ですね。
やることは単純とはいえ、手間をかけさせられますので、アクセサと併せてJavaスタイルのオブジェクト指向の嫌われている部分ではないでしょうか。ちなみにアクセサの問題は、Java以外の言語ですと、大抵、言語機能としてプロパティを持っているか、メソッド呼び出しに括弧がいらない(から後からメソッドに置き換えてOK)かのいずれかですので気にしなくていいはずです。ちなみにDelphiは両方できます。
閑話休題。インターフェースはもっと使い易くなります。そう、プロパティがアクセサを使い易くしたように。

implements指令*2という機能がDelphiのObjectPascalには存在する事をご存じでしょうか?

implementsをヘルプで引きますと、委任という項目に行き当たります。
委譲は、デザインパターンなどにも出てくる有名な言葉ですが、委任は見慣れない言葉です…。
試しに「委任のための implements の使い方」を英語版ヘルプに照らし合わせてみますと、「Using implements for delegation」というページが対応するようです。delegateは委譲の意味でも使われる言葉です。
何故Borland Japan*3は翻訳に際して新たな言葉を当てたのでしょうか。

……なんて疑問も馬鹿らしく、MicrosoftがCOM関係で委任という言葉を使ったからですね。
リンク先には集成化という言葉も出て来ますが、これはDelphiのヘルプでは集約と書かれた言葉と同義で、英語ではaggregateになります。

そんなわけで、この言葉の違いは訳の都合に過ぎません。でも微妙に違う訳語を当てていいぐらいには、委譲と委任は微妙に違った概念です。

インターフェース委譲のおさらい

まずは話の流れとしまして、インターフェースと、従来の委譲のおさらいから入らせてください。

「風来のシレン」もどきを組んでいたとします。

現在作っているのは壷クラス。
他の道具のインスタンスを詰め込んでおける道具のインスタンスのクラスです。
分裂の壷や変化の壷やンドゥバの挙動、トドバグとか見るに、「シレン」はプロトタイプベースの型無し言語が向いているというかそのものであるとは思いますけど、とにかく何故かここではDelphiで組んでるんです。
かなり無理な設定の例であることは承知していますので、許してください。

道具を指し示す型は、IToolとします。
ンドゥパやかさタヌキみたいなのがいるものの、まさかTToolTEnemyから派生するわけにもいかず、ということでインターフェースにしています。

壺は中に入っているアイテムのリストを管理しなければいけません。
しかし、インターフェースのリストを管理するコードは、ゼロから書かなくても、そのものずばりのTInterfaceListがClasses.pasに存在します。*4
よって、壷はTInterfaceListのインスタンスを内部に丸ごと抱えて、追加や取り出しなどの各操作メソッドを、そのTInterfaceListのインスタンスに丸投げすればいいことになります。
それで、壷は、内部でTInterfaceListを使っていることは知らん顔して、外向きには壷自身が必用な操作を行う能力を持っているように見せることができます。外からは、壺の実装なんてどうでもいい話ですし、実装が壺の型に影響を及ぼすべきではありません。後々TInterfaceListでは不都合があって、実装を取り替えた場合でも、壺を使う側のコードは変える必要がありません。安易にproperty List: IInterfaceList read FList;なんて作ってしまわずに、メソッドをひとつひとつ書いていくことは大事です。

……これが委譲と呼ばれる書き方ですね。

コード例は次のようなもの。
ああ、無理な設定の例であることは承知の上ですので、コンパイルが通るかどうか試したりなんてしてませんよ……その辺は了承ください。

type
  TPot = class(TShirenModokiObject, ITool, IToolsView, ...)
  private
    FList: IInterfaceList;
    ...
  protected
    procedure TakeOut(const ATool: ITool);
  public
    procedure PutIn(const ATool: ITool); virtual;
    property MaxSize: Cardinal read ...
    ...
  end;

...

procedure TPot.PutIn(const ATool: ITool);
begin
  if FList.Count = MaxSize then
    raise EShirenModokiError.Create('もう入らない')
  else
    FList.Add(ATool) {委譲}
end;

function TPot.TakeOut(const ATool: ITool);
begin
  FList.Remove(ATool) {委譲}
end;

TShirenModokiObjectはゲームに登場するオブジェクトの基底クラスでTObjectから派生してIInterfaceを実装しています。循環参照が有り得ますので、参照カウンタは使わずに、参照を辿ってGCを行う機構を独自にサポートしているか、あるいはGCライブラリ(Boehm GCなど)を使っているという解釈でも構いません。
要するに、COM風インターフェースにつきもの参照カウンタ問題は超越していますので(そういう設定なので)気にしないでください、ってことです。

TakeOutprotectedなのは、保存の壷以外は割らなきゃ取り出せないので公開は派生クラスで…というつまらん理由です。……といっても、使う場所ではこのままTPotとして使うわけではなく、あくまでインターフェース経由になりますので、単なる気分の問題です。架空の例ですが、シレンですので(ry

全ての道具はIToolとして把持されます。当然壷自体もIToolです。

道具を扱う側、要するに主人公オブジェクトその他ですが、IToolを渡された側は、その実態がTPotであることは気にしなくてOKです。
表示はIToolInstanceNameメソッドやGetIconメソッドを利用して行い、選択された時に装備できるか、物を入れられるか、中を覗けるか、等は、それぞれの能力別インターフェースをサポートしているかどうか調べれば済むからです。……そういうメソッドがあるという設定です。

……こんな風に書くと、おお、しっかり設計しているなあ、と思われるかもしれませんが、ObjectPascalの仕様ではCOMとの兼ね合いでインターフェースから実クラスにダウンキャストできない、というのも理由のひとつだったりします。*5いえ、「しっかり設計しているなあ」と思って読んでもらったほうが好都合なのですが。

ですから、道具から中身を取り出すためのインターフェースを得るためのXXX as ITakeOutableというコードは、その道具(壷)が中身を取り出せる能力を持っているかどうかの問い合わせも兼ねていて、例外を受け取ることで、ああ、この道具は中身を取り出せないんだな、ということがわかります。Supports関数もありますしね。

委任の出番です

壷は出し入れするだけでは駄目で、中を覗けなければいけません。
中身の表示は主人公の持っている道具と倉庫とで統一して次のようなインターフェースで行います。

type
  IToolsView = interface(IInterface)
    ['{2330B4DF-21EB-4FDD-BAA4-EDE73DD6922F}']
    function IsViewOnly: Boolean;
    function GetCount: Cardinal;
    function GetMaxSize: Cardinal;
    function GetAt(Index: Cardinal): ITool;
    property At[Index: Cardinal]: ITool read GetAt;
    property Count: Cardinal read GetCount;
    property MaxSize: Cardinal read GetMaxSize;
  end;

IsViewOnlyは何かといいますと、倉庫や保存の壷はユーザーが中身を選択できますが、壷には中を見るだけの壷もあります。つまりIsViewOnly = Trueのときは選択カーソルを表示しないようにします。

容量がMaxSizeなのに、現在の個数がCountと不統一なのは、ふたつのサイズを占める材料なども1個と数え……ごめんなさい。「シレン」やったことがない人には意味不明の話ですよねえ。どうでもいい部分だし、更には架空の例だし。しかし架空の例ですが、シレンですので(ry

さて、実装に際しては、インターフェースとしてリストアップされたメソッドを順当に実装していけばよさそうですが、ここで面倒くさがり屋は考えます。

このままでは、主人公の持っている道具、倉庫、壷で、3ヶ所に同じコードが必要になる。
ただでさえ長ったらしくて面倒くさいPascalコードなのに、同じことを3回も書かなければいけないのはなおさら面倒だ!

同時に言い訳も考えます。壺の実装でTInterfaceListに丸投げするだけの面倒なコードを書いてイライラが募ってます。

同じコードが3ヶ所にあるのはコードサイズが膨らんで効率が悪い。
また、共通部分を別のクラスに括りだして委譲するにしても、メソッドが4つもあったら、委譲するための横流しコードが、例え一行ずつとはいえ、けっこう肥大化を招きそうだ。
それに、もしIToolsViewにメソッドがひとつ増えてみろ、横流しコードも3ヶ所で追加しなければいけないじゃないか。
メンテナンス性も良くないぞ!

ぶつぶつ言いながらもコードは書いて、道具、倉庫、壷の共通の処理を行うヘルパークラスは完成しました。
IToolsViewが必要なクラスは必ず同時にTInterfaceListも持ちますから、一緒に管理させます。

type
  TToolList = class(TObject, IToolsView)
  private
    FList: IInterfaceList;
  public
    constructor Create;
    function IsViewOnly: Boolean;
    function GetCount: Cardinal;
    function GetMaxSize: Cardinal;
    function GetAt(Index: Cardinal): ITool;
    property List: IInterfaceList read FList;
  end;

...実装部分は省略...

早速、安易にproperty List: IInterfaceList read FList;なんてしてますが、これは実装用ヘルパーですから、表には出ませんから、と自分に言い聞かせる……。もちろんAddなりRemoveなりをちゃんと用意したほうが良いのは言うまでもありません。

TPotもこれに合わせて書き換えなければいけません。
なんか後退した感じですが、委譲メソッドをひたすら書いていくのみです。

type
  TPot = class(TShirenModokiObject, ITool, IToolsView, ...)
  private
    FList: TToolList;
    ...
    function IsViewOnly: Boolean;
    function GetCount: Cardinal;
    function GetMaxSize: Cardinal;
    function GetAt(Index: Cardinal): ITool;
  protected
    ...
  public
    ...
  end;

...

function TPot.IsViewOnly: Boolean;
begin
  Result := FList.IsViewOnly

手が止まりました。今ひとつ気が乗りません。

ここで、ふと考えます。
操作は全てインターフェース経由で行われ、そのインターフェースは常にasキャストで取得されるのですから……。

要するに、ITool型の変数(中身はTPotのインスタンス) as IToolsViewの結果として、フィールド変数FList(をIToolsViewにアップキャストしたもの)を返すことさえできれば万事解決です。

これさえできれば、横流しコードを書く手間も、横流しコードでラップする事による効率悪化も、避ける事ができます。

所詮asキャストはQueryInterfaceの呼び出しに過ぎないから、上手くQueryInterfaceをオーバーライドしてやれば……

結論からいえば(↑な事をしなくても)できます。
QueryInterfaceをoverrideするやり方は、MSDNでも見てください。
ここでは、DelphiのObjectPascalに元から備わっている言語機能を使います。
そう、都合のいい機能が存在するんです!

特定のインターフェースの実装を、他のオブジェクトに任せてしまう、これがCOMの委任です。
記述上はプロパティの宣言にimplementsキーワードを併記します。

委任される側の、つまり実際の処理を行う側のオブジェクトのクラスは、通常TAggregatedObjectから派生します。
TAggregatedObjectの参照カウンタは委任元と連動してくれるからです、が、同等の処理を書き足すなら、もしくは、参照カウンタを使わずともきちんと解放できるならば、必ずしも必要はありません。

  TToolList = class(TAggregatedObject, IToolsView) {継承元を変更}
  ...

  TPot = class(TShirenModokiObject, ITool, IToolsView, ...)
  private
    FList: TToolList;
    function GetAsToolsView: IToolsView;
    ...
  protected
    ..
  public
    ...
    property AsToolsView: IToolsView 
            read GetAsToolsView <strong>implements</strong> IToolsView; {委任!}
  end;

...

constructor TPot.Create;
begin
  ...
  FList := TToolList.Create(Self);
  FList._AddRef; {自分が参照してる分}
end;

destructor TPot.Destroy;
begin
  FList._Release; {自分が参照してる分}
  ...
end;

function TPot.GetAsToolsView: IToolsView;
begin
  Result := FList
end;

このTPodIToolsViewを継承しておきながら、IsViewOnly等の実装を持っていません。
implementsの効果で、IToolsViewが必要なときは、property AsToolsViewの中身が代わりに使用されます。

細かい話として、_AddRef_Releaseを明示的に呼んでいるのは、FListを素のクラス型として宣言しているため、一回インターフェースとして使った時点で参照カウンタが0 → 1 → 0になって解放されてしまうのを防ぐためです。
何故かというと、TToolListは実装の一部を切り出したヘルパークラスで、TPodはインターフェースとしては公開しない部分でもTToolListとやりとりを行う必要があります。そうした実装のためのメソッドやプロパティ(property Listみたいな。AddRemoveを真面目に用意した場合はそれらも)にアクセスする必要があります。
委任先オブジェクトをインターフェースとしてしか使わないのであれば、フィールドをインターフェース型にして、propertyのreadに直接そのフィールドを指定して問題ありません。

さて、こうやって委任すれば、先程の心の声が列挙したメリットを総べて享受できます。

委譲のための一行コードが要らないなんてこれは楽だ!
一行コードが実行効率を落とすなんてことも考えなくていい!
更に、もしIToolsViewにメソッドが増えても何も追加しなくていい!

……これだけの事ですが、これが、事実上、多重継承の形になっていることには気付かれたでしょうか?

has-aでありながら、ダウンキャストに応じる事ができます。
メソッド解決節を併用すれば、特定メソッドのみをTPod側で奪ったりも(override)できます。

なお、例では、幸いTInterfaceListの全機能は、IInterfaceListとしてインターフェース化されていますので、TToolsViewIInterfaceListを自身のFList: IInterfaceListに委任してしまう事も可能で、そうすればproperty Listを使うことなくFList as IInterfaceListと書けて、統一感が増すだけでなく、上手くすればTPotFList: TToolsViewの型をインターフェースにしてしまえるので_AddRef_Releaseもいらなくなります。

しかし、as演算子は直接参照を辿っていくのに比べてコストがかかりますし、型チェックがコンパイル時から実行時に遅らされます。
実装のためだけのヘルパークラスにそこまでやるのは、さすがにやりすぎな気がします。ですので、例ではそこまでやりませんでした。
やっても構わないと思います。

メソッド解決節について例を挙げておきます。
といっても、ヘルプの「クラス型プロパティへの委任」からの転載です。

type
  IMyInterface = interface
    procedure P1;
    procedure P2;
  end;
  TMyImplClass = class
    procedure P1;
    procedure P2;
  end;
  TMyClass = class(TInterfacedObject, IMyInterface)
    FMyImplClass: TMyImplClass;
    property MyImplClass: TMyImplClass read FMyImplClass implements IMyInterface;

    procedure IMyInterface.P1 = MyP1;
    procedure MyP1;
  end;
procedure TMyImplClass.P1;
  ...
procedure TMyImplClass.P2;
  ...
procedure TMyClass.MyP1;
  ...
var
  MyClass: TMyClass;
  MyInterface: IMyInterface;
begin
  MyClass := TMyClass.Create;
  MyClass.FMyImplClass := TMyImplClass.Create;
  MyInterface := MyClass;

  MyInterface.P1;        // TMyClass.MyP1 を呼び出す
  MyInterface.P2;        // TImpClass.MyP2 を呼び出す
end;

overrideの動きになっているのが確認できます。
つまり、implementsは、ただ単にas演算子の結果を置き換えるだけではなくて、もう少し踏み込んで多重継承めいた動きをさせることができます。
ただ、TMyImplClassの内側で自分のP1を呼んだ場合は、やっぱり呼ばれるのはTMyImplClassP1に過ぎません。
委任された側の動きは変わらないわけで、やっぱり本物の多重継承とは少し異なります。

メソッド解決節……瑣末ながら実際には必要な機能

メソッド解決節は、本当はimplementsのための機能ではなくて、インターフェース間の衝突を回避するためのものです。
脇道にそれますが、説明させてください。

全然関係ないふたつのライブラリが、それぞれインターフェースIAIBを宣言しているとしましょう。
両方とも、Executeというよくある名前のメソッドを要求しています。

type IA = interface
  procedure Execute;
end;
type IB = interface
  procedure Execute;
end;

ひとつ目のライブラリを使うためには、インターフェースIAを自分のクラスに実装する必要があります。
ふたつ目のライブラリを使うためには……もうおわかりですね。

Javaをはじめ、インターフェースを採用している多くのOOPLでは、この状況を解決する方法がありません。
Executeというよくある名前を使っていることについても責められません。クラスやインターフェースは名前空間になり、衝突は気にしなくていい、という建前があるからです。
しかし、実際には衝突してしまいます。

私の知る限り、これを解決できるのは、C#とEiffelとObjectPascal(Delphi)のみです。
多くのOOPLが、安易にJavaの真似をしてインターフェースを導入しては、メソッド名をグローバルなものに引き戻してしまっています。
片手落ちであり、欠陥と言い切って良いと思います。

type T = class(TObject, IA, IB)
  procedure IA.Execute = IA_Execute;
  procedure IA_Execute;
  procedure IB.Execute = IB_Execute;
  procedure IB_Execute;
end;

多重継承とその代替としてのインターフェース

例はこの辺にして、多重継承に立ち返りましょう。

多重継承の一番の問題点は、やはり菱形継承ではないでしょうか。
菱形継承とは、クラスAからBとCが派生し、DはBとCを多重継承しているような状況です。

それだけなら特に問題でも無さそうなのですが、ここで、Aで定義された仮想メソッドMを、BとCが独自にオーバーライドしていたとします。
さて、Dのインスタンスに対してMを呼んだ場合、BとCでオーバーライドした内容のどちらが呼び出されるのが正しいでしょう?

Eiffelは、共通の基底クラスを持ち、なおかつ多重継承可能な言語のため、多重継承は必ず菱形継承になります。
そのため、このような衝突が起きた場合どちらを優先させるか選択する構文があります。メソッド解決節に近いですね。

多重継承の代替手段としてのインターフェースに視点を移します。

インターフェースは、「できること」を「メソッドのセット」で表して、各クラスはそれを実装するという形を取ります。

そもそも、合成の壷だって、先に入れた方が、合成後のメインになります。
前者をベースに、後者の能力だけを合成しています。
回復の剣のような「特定の組み合わせからなる役」は除きますよ。

等価なものを等価に合成するのは……現実では化学変化でどうとでもなるでしょうが……プログラムとしてはややこしくならざるを得ません。

同じ能力を何度取り込んでも、クラスは単一継承のみですから各メソッドは同じ実装に結びつくので、問題が起こりません。

よって、全てのインターフェースが、共通のインターフェースを含む形にしても問題が無いことになり、菱形継承の形であっても、インターフェース全般を差すポインタ(ObjectPascalの場合はIInterface、C#の場合はインターフェースもObject型でポイントできます)の使用や、インターフェース間の継承も特にややこしいことなく行えます。

インターフェースを能力の合成として捉えると、色々と単純になります。

例えば、メインの継承階層が 生物→動物→哺乳類→霊長類→人間ならば、
動物の段階で動けるようにし、
哺乳類の段階で食事ができるようにし、
霊長類の段階で手で物をつかめるようにし、
人間の段階で言語を話せるようにする、
という具合です。

これは、現実の進化を完全に反映しているわけではありませんが、少なくとも普通にプログラムを組む時の頭の中のモデルの反映としては、結構イイ形です。

しかし、複数のクラスにまたがって同じ実装を与えたい場合、「メソッドのセット」として定義されているメソッド全ての中身を、全部横流しの一行関数を書いて委譲していく面倒な手順が必用です。同じ能力に対して同じ実装を与えたい場合が多々あるのは当たり前ですよね。

その解決方法が、mix-inであり、委任である…というわけです。

単一継承との比較

もっと単純に、Ada95のように、またはDelphi2以前のように、或いはその他いくつかの言語のように、全ての継承は単一継承に限定して、能力はhas-aで代用する、という考え方もあります。
この場合、霊長類 is 手で物をつかめるではなく、霊長類 has 手で物をつかむ能力となります。

……この方法は、実用上幾つか問題があります……重箱の隅のような話で申し訳ないのですが。

まず、能力クラスで定義された仮想メソッドを簡単にオーバーライドできないということ。
わざわざ手で物をつかむ能力から派生して霊長類専用手で物をつかむ能力を作らないと、能力の持ち主となる霊長類との連携が取れません。
インターフェース導入以前のデザインであるTStringsがこのようになっています。

次に、一般的な問い合わせ方法が存在しないことです。
とある生物への参照があって、それが手で物をつかむ能力を持っているかどうか調べたいとします。
オブジェクトと能力の間にis-a関係があれば、言語のサポート(as演算子)で問い合わせることができます。
しかし、has-aで表現されていると、問い合わせの手段がありません。
生物霊長類かどうか調べるだけでは不足です。リスだって手で物をつかめますから。
あ、TStringsにはこの批判はあたりませんよ……そもそもTStringsはコンテナであって能力ではないですから。別にTStringsをふたつ持っているクラスがあってもいいわけですし。

mix-inとの比較

問題の無い多重継承の代替、ということでは、他にも、D, Ruby等のmix-inが挙げられます。

Delphi式implementsの対mix-inメリットとしては、プロパティに便乗する形で追加された言語機能ですので、委任先をフィールドでは無くGetterメソッドから読み出せます。
ということは、必要とされるまで生成を遅らせたり、委任先を実行時に交換したりできる、ということです。

一部機能に、大量にメモリを消費するが、インスタンスによっては要らない場合がある…なんて時に、Getterが呼ばれた時点で初めて委任先を生成する、なんてことが考えられます。

ゲームのコントローラーからの入力をゲームオブジェクトへ送る場合、ゲームオブジェクトは実は入力を受理するインターフェースを他のクラスへ委任してて(実は、と書いたのは、委任してようが自前で実装してようが外から使うぶんには区別つきませんから)、サーバーモードでは送られてきたデータを扱うクラスAへ、クライアントモードでは直接ゲームパッドを扱うクラスBへ、それぞれ委任している…なんてパターンも考えられます。

インターフェースと実装クラスが別れているため、実装クラスを単体で使ったり、委任先を実行時に取り替えたりと、組み合わせが自由自在なのは大きなメリットじゃないでしょうか。*6

なお、mix-inは、C++でも使えます。
C++では、共通の基底クラスを持たないため、わざわざ菱形継承にさえしなければそれでいいです。
名前の衝突を回避する方法はありませんけどね……。

Duck-Typingとの比較

Duck-Typingは、そもそもの仕組み上、名前の衝突を回避する方法が無いし、追加しようもないじゃないですか……。

集約

上でも触れたように、COMには、集約と呼ばれる技法があります。
様々な機能(インターフェース)を特定のクラスに集中する、COM特有(?)のパターンです。

特定の参照が欲しい時に、GetXXXを呼び分けるのでは無く、QueryInterfaceで自分以外のオブジェクトの参照を返してしまおう…と、まあ、やってることは同じです。

しかし、意図している意味合いは異なります……と思います。
集約は、多重継承を意図してはいません。
僕の探し方が悪いに違いないのですが、MSDNオンラインでは有用なドキュメントを見つけられませんでしたので、意義などをてきとーに想像して書いてみます。

GetXXX方式ですと、種類を増やすたびに、メソッドを増やす事になるため、公開インターフェースを書き換えないといけません。
該当インターフェースを書き換えたくない、書き換える事ができない時もあるでしょう。
それに、インターフェースを書き換えてしまうとバイナリ互換性が崩れます。

GetXXXをやめて、全部QueryInterfaceで取得可能にすればどうでしょうか?

とりあえずこのオブジェクトに問い合わせればOK、みたいな機能の根っことなるクラスを用意して、個々の機能はQueryInterfaceで取得して貰います。
もちろん実装は機能のかたまりごとに、書き易い単位でクラスを分けて書くわけです。

COMならばQueryInterfaceを自前で実装しますので、どの言語を使っても集約は可能ですが、その言語そのものがサポートしているダウンキャスト演算子で同じことをやろうとすれば、implementsのような機能が必要な事は明らかです。

……なんでもかんでもas演算子でOKってのは、安直で、型安全を損ねて、実行効率もちょっと悪いですが、いちいちGetXXXというメソッド名(Delphiで書くなら無論プロパティにしますよ)を覚えずに済みますから、ちょっとだけ気楽です。

僕はIDEの中身まで知りませんが、Open Tools APIのBorlandIDEServices変数ってきっとそういうノリなんだろうと想像しています。

C++Builderでのimplements

すごく長い間、implementsはDelphiの持つ大きなアドバンテージでした。
それが覆ろうとしています……??

XE 向けに行われた C++Builder の変更点を見てください……。

__property の拡張構文を利用すると、C++ でのインターフェイスの実装が簡単にできます:
C++Builder には、新しい implements 属性を追加するための、_property キーワード用の拡張構文があります。  implements 属性を利用すると、継承を使用せずに TObject ベースのクラスにインターフェイスを実装できます。 __property implements を使用すると、C++Builder XE の ActiveX プロジェクトの ATL から DAX への移行が簡単になります。また、DAX によって提供される ActiveX ヘルパの使用が簡単になります。 「C++Builder XE における __property implements サポート」を参照してください。

これは!?
早速C++Builder XEの体験版をインストールして、試してみました。
結果はこのコードです。

できることとしては、QueryInterfaceより更に劣っているみたいで、まだまだDelphiのアドバンテージは安泰なようです!?
C++なら普通に多重継承いやなんでもないです。