12

次のインターフェイスとそれらを実装するクラスがある場合-

IBase = Interface ['{82F1F81A-A408-448B-A194-DCED9A7E4FF7}']
End;

IDerived = Interface(IBase) ['{A0313EBE-C50D-4857-B324-8C0670C8252A}']
End;

TImplementation = Class(TInterfacedObject, IDerived)
End;

次のコードは「Bad!」を出力します -

Procedure Test;
Var
    A : IDerived;
Begin
    A := TImplementation.Create As IDerived;
    If Supports (A, IBase) Then
        WriteLn ('Good!')
    Else
        WriteLn ('Bad!');
End;

これは少し面倒ですが理解できます。IBaseはTImplementationがサポートするGUIDのリストに含まれていないため、サポートをIBaseにキャストできません。宣言を-に変更することで修正できます。

TImplementation = Class(TInterfacedObject, IDerived, IBase)

それでも、それをしなくても、AはIDerivedであり、IDerivedはIBaseであるため、AがIBaseを実装していることはすでに知っています。したがって、チェックを省略した場合、Aをキャストでき、すべてが正常になります-

Procedure Test;
Var
    A : IDerived;
    B : IBase;
Begin
    A := TImplementation.Create As IDerived;
    B := IBase(A);
    //Can now successfully call any of B's methods
End;

しかし、IBasesを汎用コンテナ(たとえばTInterfaceList)に入れ始めると、問題が発生します。IInterfacesしか保持できないため、キャストを行う必要があります。

Procedure Test2;
Var
    A : IDerived;
    B : IBase;
    List : TInterfaceList;
Begin
    A := TImplementation.Create As IDerived;
    B := IBase(A);

    List := TInterfaceList.Create;
    List.Add(IInterface(B));
    Assert (Supports (List[0], IBase)); //This assertion fails
    IBase(List[0]).DoWhatever; //Assuming I declared DoWhatever in IBase, this works fine, but it is not type-safe

    List.Free;
End;

不一致の型をキャッチするために、ある種のアサーションが必要です。この種のことは、Is演算子を使用してオブジェクトで実行できますが、インターフェイスでは機能しません。さまざまな理由から、サポートされているインターフェイスのリストにIBaseを明示的に追加したくありません。ハードキャストIBase(List [0])が安全な方法である場合に、真と評価されるような方法でTImplementationとアサーションを記述できる方法はありますか?

編集:

答えの1つに出てきたので、TImplementationが実装するインターフェイスのリストにIBaseを追加したくない2つの主な理由を追加します。

第一に、それは実際には問題を解決しません。Test2で、式が次の場合:

Supports (List[0], IBase)

trueを返します。これは、ハードキャストを実行しても安全であることを意味するものではありません。QueryInterfaceは、要求されたインターフェースを満たすために別のポインターを返す場合があります。たとえば、TImplementationがIBaseとIDerived(およびIInterface)の両方を明示的に実装している場合、アサーションは正常に渡されます。

Assert (Supports (List[0], IBase)); //Passes, List[0] does implement IBase

しかし、誰かが誤ってアイテムをIInterfaceとしてリストに追加したと想像してください

List.Add(Item As IInterface);

アサーションは引き続き通過します-アイテムは引き続きIBaseを実装しますが、リストに追加された参照はIInterfaceのみです-IBaseにハードキャストしても意味のあるものは何も生成されないため、次のハード-キャストは安全です。動作が保証されている唯一の方法は、キャストまたはサポートを使用することです。

(List[0] As IBase).DoWhatever;

ただし、これは、リストに項目を追加してIBaseタイプであることを確認するコードの責任であるため、イライラするパフォーマンスコストです。これを想定できるはずです(したがって、この想定がfalse)。誰かがいくつかのタイプを変更した場合に後で間違いを見つけることを除いて、アサーションは必要さえありません。この問題の元のコードもかなりパフォーマンスが重要であるため、ほとんど達成されないパフォーマンスコスト(実行時に不一致の型をキャッチするだけですが、より高速なリリースビルドをコンパイルする可能性はありません)は避けたいものです。

2番目の理由は、参照が等しいかどうかを比較できるようにしたいのですが、同じ実装オブジェクトが異なるVMTオフセットを持つ異なる参照によって保持されている場合、これは実行できません。

編集2:例を使用して上記の編集を拡張しました。

編集3:注:問題は、ハードキャストを回避する方法ではなく、アサーションが通過した場合にハードキャストが安全になるようにアサーションをどのように定式化できるかです。ハードキャストステップを別の方法で実行する方法、または完全に回避する方法はありますが、実行時のパフォーマンスコストがかかる場合、それらを使用することはできません。後でコンパイルできるように、アサーション内のチェックのすべてのコストが必要です。

そうは言っても、誰かがパフォーマンスコストやタイプチェックの危険性なしに問題を完全に回避できれば、それは素晴らしいことです!

4

3 に答える 3

12

できることの1つは、型キャストインターフェイスを停止することです。からに移動するためにそれを行う必要はありません。また、からに移動するためにもそれを行うIDerived必要IBaseはありません。への参照すでに存在するため、型キャストしなくてもメソッドを呼び出すことができます。型キャストを少なくすると、コンパイラーに多くの作業を任せて、正しくないものをキャッチすることができます。IBaseIUnknownIDerived IBaseIBase

あなたが述べた目標は、あなたがあなたのリストから得ているものが本当にIBase参照であるかどうかをチェックできるようにすることです。実装されたインターフェースとして追加するIBaseと、その目標を簡単に達成できます。その観点から、それを行わないためのあなたの「2つの主な理由」は水を保持しません。

  1. 「参照が等しいかどうかを比較できるようにしたい」:問題ありません。COMではQueryInterface、同じオブジェクトで同じGUIDを使用して2回呼び出すと、両方の時間で同じインターフェイスポインターを取得する必要があります。2つの任意のインターフェイス参照があり、as両方をIBaseにキャストすると、同じオブジェクトによってサポートされている場合にのみ、結果は同じポインター値になります。

    リストに値のみを含めるIBase必要があり、ジェネリックTInterfaceList<IBase>が役立つDelphi 2009がないIBaseため、子孫タイプの値ではなく、常に明示的に値をリストに追加するように訓練することができます。リストにアイテムを追加するときは常に、次のようなコードを使用してください。

    List.Add(Item as IBase);
    

    そうすれば、リスト内の重複を簡単に検出でき、「ハードキャスト」が確実に機能します。

  2. 「それは実際には問題を解決しません」:しかし、上記のルールを考えると、それは解決します。

    Assert(Supports(List[i], IBase));
    

    オブジェクトがそのすべてのインターフェースを明示的に実装している場合、そのようなことを確認できます。また、上記のようにリストに項目を追加した場合は、アサーションを無効にしても安全です。アサーションを有効にすると、誰かがプログラムの他の場所でコードを変更して、リストにアイテムを誤って追加したことを検出できます。ユニットテストを頻繁に実行すると、問題が発生してすぐに問題を検出することもできます。

上記の点を念頭に置いて、リストに追加されたものが次のコードで正しく追加されたことを確認できます。

var
  AssertionItem: IBase;

Assert(Supports(List[i], IBase, AssertionItem)
       and (AssertionItem = List[i]));
// I don't recall whether the compiler accepts comparing an IBase
// value (AssertionItem) to an IUnknown value (List[i]). If the
// compiler complains, then simply change the declaration to
// IUnknown instead; the Supports function won't notice.

アサーションが失敗した場合は、まったくサポートしていないものをリストにIBase追加したか、一部のオブジェクトに追加した特定のインターフェイス参照を参照として使用できませんIBaseList[i]アサーションが合格した場合、それが有効な値を与えることがわかりIBaseます。

リストに追加される値は、明示的に値である必要はないことに注意してください。IBase上記の型宣言を考えると、これは安全です。

var
  A: IDerived;
begin
  A := TImplementation.Create;
  List.Add(A);
end;

TImplementationによって実装されたインターフェースは、単純なリストに縮退する継承ツリーを形成するため、これは安全です。2つのインターフェースが相互に継承しないが、共通の祖先を持つブランチはありません。の子孫が2つIBaseあり、両方を実装した場合、保持されている参照が必ずしもそのオブジェクトの「正規の」参照であるとは限らないTImplementationため、上記のコードは有効ではありません。アサーションはその問題を検出するので、代わりにそれを追加する必要があります。IBaseAIBaseList.Add(A as IBase)

アサーションを無効にすると、タイプを正しく取得するためのコストは、リストからの読み取り中ではなく、リストへの追加中にのみ支払われます。AssertionItem手順の他の場所でその変数を使用しないように、変数に名前を付けました。アサーションをサポートするためだけにあり、アサーションが無効になると有効な値はありません。

于 2009-04-16T12:54:49.243 に答える
6

あなたはあなたの検査に正解であり、私が知る限り、あなたが遭遇した問題に対する直接的な解決策は実際にはありません。その理由は、インターフェース間の継承の性質にあります。これは、クラス間の継承に漠然と似ているだけです。継承されたインターフェースはまったく新しいインターフェースであり、継承元のインターフェースと共通のメソッドがいくつかありますが、直接接続はありません。したがって、基本クラスインターフェイスを実装しないことを選択することにより、コンパイルされたプログラムが従うという特定の仮定を立てることになります。TImplementationはIBaseを実装しません。「インターフェースの継承」は多少誤称だと思いますが、インターフェースの拡張の方が理にかなっています。一般的な方法は、基本インターフェイスを実装する基本クラスを用意することであり、拡張インターフェイスを実装する派生クラスよりも、ただし、両方を実装する別のクラスが必要な場合は、それらのインターフェイスをリストするだけです。使用を避けたい特定の理由があります。

TImplementation = Class(TInterfacedObject, IDerived, IBase)

またはあなたはそれが好きではありませんか?

さらなるコメント

ハードタイプでさえインターフェースをキャストしてはいけません。インターフェイスで「as」を実行すると、オブジェクトのvtableポインタが正しい方法で調整されます...ハードキャストを実行すると(そして呼び出すメソッドがある場合)、コードが簡単にクラッシュする可能性があります。私の印象では、インターフェイスをオブジェクトのように扱っていますが(継承とキャストを同じように使用しています)、内部の動作は実際には異なります。

于 2009-04-16T07:31:39.560 に答える
0

Test2では;

IDerivedをIBase(A)によってIBaseとして再入力するのではなく、次のようにします。

Supports(A, IBase, B);

そして、リストへの追加は次のようになります。

List.Add(B);
于 2009-04-16T07:27:10.117 に答える