暗黙的インターフェイスと明示的インターフェイス


9

コンパイル時のポリモーフィズムと実行時のポリモーフィズムの実際の制限を理解していると思います。しかし、明示的なインターフェイス(実行時の多態性、つまり仮想関数とポインタ/参照)と暗黙的なインターフェイス(コンパイル時の多態性、つまりテンプレート)の概念的な違いは何ですか

私の考えでは、同じ明示的インターフェースを提供する2つのオブジェクトは同じタイプのオブジェクトである(または共通の祖先を持っている)必要がありますが、同じ暗黙的インターフェースを提供する2つのオブジェクトは同じタイプのオブジェクトである必要はありません。両方が提供するインターフェースは、まったく異なる機能を持つことができます。

これについて何か考えはありますか?

また、2つのオブジェクトが同じ暗黙のインターフェイスを提供する場合、これらのオブジェクトがそのインターフェイスを宣言する基本オブジェクトから継承しないようにする理由(仮想関数ルックアップテーブルなどの動的ディスパッチが不要であるという技術的な利点以外)それを明示的なインターフェースにしていますか?別の言い方をすると、同じ暗黙のインターフェースを提供する(したがって、サンプルテンプレートクラスの型として使用できる)2つのオブジェクトが、そのインターフェースを明示的にする基本クラスから継承しないようにできますか?

関連する投稿:


この質問をより具体的にする例を次に示します。

暗黙的なインターフェース:

class Class1
{
public:
  void interfaceFunc();
  void otherFunc1();
};

class Class2
{
public:
  void interfaceFunc();
  void otherFunc2();
};

template <typename T>
class UseClass
{
public:
  void run(T & obj)
  {
    obj.interfaceFunc();
  }
};

明示的なインターフェース:

class InterfaceClass
{
public:
  virtual void interfaceFunc() = 0;
};

class Class1 : public InterfaceClass
{
public:
  virtual void interfaceFunc();
  void otherFunc1();
};

class Class2 : public InterfaceClass
{
public:
  virtual void interfaceFunc();
  void otherFunc2();
};

class UseClass
{
public:
  void run(InterfaceClass & obj)
  {
    obj.interfaceFunc();
  }
};

さらに詳細な具体例:

一部のC ++の問題は、次のいずれかで解決できます。

  1. テンプレートタイプが暗黙的なインターフェイスを提供するテンプレートクラス
  2. 明示的なインターフェイスを提供する基本クラスポインターを受け取る非テンプレートクラス

変更されないコード:

class CoolClass
{
public:
  virtual void doSomethingCool() = 0;
  virtual void worthless() = 0;
};

class CoolA : public CoolClass
{
public:
  virtual void doSomethingCool()
  { /* Do cool stuff that an A would do */ }

  virtual void worthless()
  { /* Worthless, but must be implemented */ }
};

class CoolB : public CoolClass
{
public:
  virtual void doSomethingCool()
  { /* Do cool stuff that a B would do */ }

  virtual void worthless()
  { /* Worthless, but must be implemented */ }
};

事例1。明示的なインターフェイスを提供する基本クラスポインターを取る非テンプレートクラス:

class CoolClassUser
{
public:  
  void useCoolClass(CoolClass * coolClass)
  { coolClass.doSomethingCool(); }
};

int main()
{
  CoolA * c1 = new CoolClass;
  CoolB * c2 = new CoolClass;

  CoolClassUser user;
  user.useCoolClass(c1);
  user.useCoolClass(c2);

  return 0;
}

事例2。テンプレートタイプが暗黙的なインターフェースを提供するテンプレートクラス:

template <typename T>
class CoolClassUser
{
public:  
  void useCoolClass(T * coolClass)
  { coolClass->doSomethingCool(); }
};

int main()
{
  CoolA * c1 = new CoolClass;
  CoolB * c2 = new CoolClass;

  CoolClassUser<CoolClass> user;
  user.useCoolClass(c1);
  user.useCoolClass(c2);

  return 0;
}

事例3。そのテンプレートタイプテンプレートクラスではないから派生する暗黙のインターフェース(この時間を、提供しますCoolClass

class RandomClass
{
public:
  void doSomethingCool()
  { /* Do cool stuff that a RandomClass would do */ }

  // I don't have to implement worthless()! Na na na na na!
}


template <typename T>
class CoolClassUser
{
public:  
  void useCoolClass(T * coolClass)
  { coolClass->doSomethingCool(); }
};

int main()
{
  RandomClass * c1 = new RandomClass;
  RandomClass * c2 = new RandomClass;

  CoolClassUser<RandomClass> user;
  user.useCoolClass(c1);
  user.useCoolClass(c2);

  return 0;
}

ケース1では、渡されるオブジェクトuseCoolClass()がの子であるCoolClass(およびを実装するworthless())必要があります。一方、ケース2と3 は、関数を持つ任意のクラスを取りdoSomethingCool()ます。

コードのユーザーが常に細かいサブクラス化を行っているCoolClass場合、ケース1は直感的に理解できます。なぜなら、CoolClassUserは常にの実装を期待しているからCoolClassです。しかし、このコードがAPIフレームワークの一部になると想定すると、ユーザーが関数CoolClassを持つ独自のクラスをサブクラス化するかロールするかを予測できませんdoSomethingCool()


多分私は何かが足りないかもしれませんが、重要な違いは最初の段落ですでに簡潔に述べられていませんか?
Robert Harvey、

2
抽象クラス(明示的なインターフェイスを提供する)へのポインターを受け取るクラスまたは関数を使用するか、暗黙的なインターフェイスを提供するオブジェクトを使用するテンプレートクラスまたは関数を使用することで解決できる問題がいくつかあります。どちらのソリューションでも機能します。最初のソリューションをいつ使用しますか?二番目?
Chris Morris

概念をもう少し開くと、これらの考慮事項のほとんどがバラバラになると思います。たとえば、非継承静的ポリモーフィズムはどこに適合しますか?
ハビエル

回答:


8

重要なポイントはすでに定義されています。1つは実行時で、もう1つはコンパイル時です。あなたが必要とする本当の情報は、この選択の結果です。

コンパイル時:

  • メリット:コンパイル時のインターフェースは、ランタイムのインターフェースよりもはるかに詳細です。つまり、1つの関数または関数のセットの要件を呼び出すときにのみ使用できるということです。インターフェース全体を常に実行する必要はありません。要件は、必要なものだけです。
  • 長所:CRTPのような手法では、暗黙のインターフェイスを使用して、演算子などのデフォルトの実装を行うことができます。実行時の継承では、そのようなことは決してできません。
  • 利点:暗黙的なインターフェイスは、ランタイムインターフェイスよりも「継承」の作成と乗算がはるかに簡単であり、バイナリ制限を課すことはありません。たとえば、PODクラスは暗黙的なインターフェイスを使用できます。virtual継承や暗黙のインターフェースを持つ他の悪意のあるものは必要ありません- 大きな利点です。
  • 長所:コンパイラーは、コンパイル時のインターフェースをさらに最適化できます。さらに、追加の型安全性により、コードがより安全になります。
  • 長所:最終的なオブジェクトのサイズや配置がわからないため、ランタイムインターフェイスで値の型指定を行うことはできません。これは、値の型付けが必要である/メリットがあるすべてのケースが、テンプレートから大きなメリットを得ることを意味します。
  • 短所:テンプレートはコンパイルして使用するのに苦手で、コンパイラ間で面倒に移植することができます
  • 短所:テンプレートは実行時に(明らかに)ロードできないため、たとえば、動的データ構造の表現には制限があります。

ランタイム:

  • プロ:最終的なタイプは、実行時まで決定する必要はありません。これは、テンプレートがそれを行うことができれば、実行時の継承が一部のデータ構造をはるかに簡単に表現できることを意味します。また、COMなど、Cの境界を越えて実行時の多相型をエクスポートすることもできます。
  • プロ:実行時の継承を指定して実装する方がはるかに簡単で、コンパイラ固有の動作を実際に得ることはありません。
  • 短所:実行時の継承は、コンパイル時の継承よりも遅くなる可能性があります。
  • 欠点:実行時の継承では型情報が失われます。
  • 欠点:実行時の継承は、柔軟性に欠けます。
  • 短所:多重継承は雌犬です。

相対リストを考慮して、ランタイム継承の特定の利点が必要ない場合は、使用しないでください。テンプレートよりも遅く、柔軟性が低く、安全性が低くなります。

編集:C ++では特に、ランタイムポリモーフィズム以外の継承用途があることに注意してください。たとえば、typedefを継承したり、型のタグ付けに使用したり、CRTPを使用したりできます。ただし、これらの手法(およびその他の手法)は、を使用して実装されていても、最終的には「コンパイル時」に該当しclass X : public Yます。


コンパイル時の最初のプロに関して、これは私の主な質問の1つに関連しています。明示的なインターフェイスでのみ作業することを明確にしたいですか?つまり。「私が必要なすべての機能を持っているかどうかは気にしません。クラスZを継承しない場合は、何もしません。」また、実行時の継承では、ポインタ/参照を使用しても型情報が失われませんか?
Chris Morris、

@ChrisMorris:いいえ。機能する場合は機能します。なぜ誰かにまったく同じコードを他の場所に書かせるのですか?
jmoreno

1
@ChrisMorris:いいえ、私はしません。Xだけが必要な場合は、カプセル化の基本的な基本原則の1つであり、Xを要求して気にするだけです。また、型情報も失われます。たとえば、そのようなタイプのオブジェクトをスタックに割り当てることはできません。本当のタイプでテンプレートをインスタンス化することはできません。それらに対してテンプレート化されたメンバー関数を呼び出すことはできません。
DeadMG

あるクラスを使用するクラスQがある場合はどうでしょうか。Qはテンプレートパラメータを取るので、暗黙のインターフェイスを提供するすべてのクラスが機能します。クラスQも、その内部クラス(Hと呼ぶ)がQのインターフェースを使用することを期待していることがわかります。たとえば、Hオブジェクトが破棄されると、Qの関数を呼び出す必要があります。これは暗黙のインターフェースでは指定できません。したがって、テンプレートは失敗します。より明確に言えば、相互に暗黙のインターフェイス以上のものを必要とする密に結合されたクラスのセットは、テンプレートの使用を禁止しているようです。
Chris Morris

Conコンパイル時:デバッグするには醜い、定義をヘッダーに入れる必要がある
JFFIGK
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.