この文章は以前「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
とします。
ンドゥパやかさタヌキみたいなのがいるものの、まさかTTool
をTEnemy
から派生するわけにもいかず、ということでインターフェースにしています。
壺は中に入っているアイテムのリストを管理しなければいけません。
しかし、インターフェースのリストを管理するコードは、ゼロから書かなくても、そのものずばりの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風インターフェースにつきもの参照カウンタ問題は超越していますので(そういう設定なので)気にしないでください、ってことです。
TakeOut
がprotected
なのは、保存の壷以外は割らなきゃ取り出せないので公開は派生クラスで…というつまらん理由です。……といっても、使う場所ではこのままTPot
として使うわけではなく、あくまでインターフェース経由になりますので、単なる気分の問題です。架空の例ですが、シレンですので(ry
全ての道具はITool
として把持されます。当然壷自体もITool
です。
道具を扱う側、要するに主人公オブジェクトその他ですが、ITool
を渡された側は、その実態がTPot
であることは気にしなくてOKです。
表示はITool
のInstanceName
メソッドや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;
このTPod
はIToolsView
を継承しておきながら、IsViewOnly
等の実装を持っていません。implements
の効果で、IToolsView
が必要なときは、property AsToolsView
の中身が代わりに使用されます。
細かい話として、_AddRef
と_Release
を明示的に呼んでいるのは、FList
を素のクラス型として宣言しているため、一回インターフェースとして使った時点で参照カウンタが0 → 1 → 0になって解放されてしまうのを防ぐためです。
何故かというと、TToolList
は実装の一部を切り出したヘルパークラスで、TPod
はインターフェースとしては公開しない部分でもTToolList
とやりとりを行う必要があります。そうした実装のためのメソッドやプロパティ(property List
みたいな。Add
やRemove
を真面目に用意した場合はそれらも)にアクセスする必要があります。
委任先オブジェクトをインターフェースとしてしか使わないのであれば、フィールドをインターフェース型にして、propertyのreadに直接そのフィールドを指定して問題ありません。
さて、こうやって委任すれば、先程の心の声が列挙したメリットを総べて享受できます。
委譲のための一行コードが要らないなんてこれは楽だ!
一行コードが実行効率を落とすなんてことも考えなくていい!
更に、もしIToolsView
にメソッドが増えても何も追加しなくていい!
……これだけの事ですが、これが、事実上、多重継承の形になっていることには気付かれたでしょうか?
has-aでありながら、ダウンキャストに応じる事ができます。
メソッド解決節を併用すれば、特定メソッドのみをTPod
側で奪ったりも(override)できます。
なお、例では、幸いTInterfaceList
の全機能は、IInterfaceList
としてインターフェース化されていますので、TToolsView
がIInterfaceList
を自身のFList: IInterfaceList
に委任してしまう事も可能で、そうすればproperty List
を使うことなくFList as IInterfaceList
と書けて、統一感が増すだけでなく、上手くすればTPot
のFList: 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
を呼んだ場合は、やっぱり呼ばれるのはTMyImplClass
のP1
に過ぎません。
委任された側の動きは変わらないわけで、やっぱり本物の多重継承とは少し異なります。
メソッド解決節……瑣末ながら実際には必要な機能
メソッド解決節は、本当はimplements
のための機能ではなくて、インターフェース間の衝突を回避するためのものです。
脇道にそれますが、説明させてください。
全然関係ないふたつのライブラリが、それぞれインターフェースIA
とIB
を宣言しているとしましょう。
両方とも、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の体験版をインストールして、試してみました。
結果はこのコードです。
dynamic_cast
ではなくてSystem::interface_cast
を使う、というよりC++BuilderからDelphiスタイルインターフェースを使うのは案外面倒くさいですね……そもそもC++Builderでこういう事をするのも初めてなんですけれども。- メソッド解決節がない、というよりTがIを明示的に継承している形にはできないみたいです。
implements
も具体的にどのインターフェースを実装するかは書けずに、プロパティの型が使われます。 - 一番大きい制限として、プロパティのreadにメソッドを指定できませんでした。メンバ変数だけ。これではできることがだいぶ限られてしまいます。
- C++0xの
[[override]]
属性は、構文としては受け付けてくれますが、実際には無視されているようです。警告が出ました。(implements
関係ない^^;)
できることとしては、QueryInterface
より更に劣っているみたいで、まだまだDelphiのアドバンテージは安泰なようです!?C++なら普通に多重継承いやなんでもないです。