web-dev-qa-db-ja.com

C ++の多態性

私の知る限り:

C++は、3種類のポリモーフィズムを提供します。

  • 仮想機能
  • 関数名のオーバーロード
  • 演算子のオーバーロード

上記の3種類の多型に加えて、他の種類の多型が存在します。

  • ランタイム
  • コンパイル時
  • アドホック多型
  • パラメトリック多型

ランタイムポリモーフィズムは、仮想関数および静的ポリモーフィズムによって達成できることを知っていますテンプレート関数によって実現できます

しかし、他の2つ

  • アドホック多型
  • パラメトリック多型 website says

アドホック多型:

使用できる実際のタイプの範囲が有限であり、使用する前に組み合わせを個別に指定する必要がある場合、これはアドホックポリモーフィズムと呼ばれます。

パラメトリック多型:

すべてのコードが特定のタイプに言及せずに記述されているため、任意の数の新しいタイプで透過的に使用できる場合、それはパラメトリックポリモーフィズムと呼ばれます。

私はそれらをほとんど理解できません:(

可能であれば両方とも例を挙げて説明できますか?この質問に対する答えが、大学の多くの新しい合格者に役立つことを願っています。

123
Vijay

ポリモーフィズムの理解/要件

多態性を理解するには-用語がコンピューティングサイエンスで使用されているように-単純なテストと定義から始めるのが役立ちます。考慮してください:

    Type1 x;
    Type2 y;

    f(x);
    f(y);

ここで、f()は何らかの操作を実行するためのものであり、入力としてxおよびyの値が与えられています。

ポリモーフィズムを示すには、f()が少なくとも2つのdistinct型の値(たとえばintおよびdouble)、個別のタイプに適したコードを見つけて実行します。


ポリモーフィズムのC++メカニズム

プログラマーが明示的に指定したポリモーフィズム

f()は、次のいずれかの方法で複数の型を操作できるように記述できます。

  • 前処理:

    #define f(X) ((X) += 2)
    // (note: in real code, use a longer uppercase name for a macro!)
    
  • 過負荷:

    void f(int& x)    { x += 2; }
    
    void f(double& x) { x += 2; }
    
  • テンプレート:

    template <typename T>
    void f(T& x) { x += 2; }
    
  • 仮想ディスパッチ:

    struct Base { virtual Base& operator+=(int) = 0; };
    
    struct X : Base
    {
        X(int n) : n_(n) { }
        X& operator+=(int n) { n_ += n; return *this; }
        int n_;
    };
    
    struct Y : Base
    {
        Y(double n) : n_(n) { }
        Y& operator+=(int n) { n_ += n; return *this; }
        double n_;
    };
    
    void f(Base& x) { x += 2; } // run-time polymorphic dispatch
    

その他の関連メカニズム

組み込み型、標準変換、およびキャスト/強制のためのコンパイラ提供のポリモーフィズムについては、完全性のために以下で説明します。

  • とにかく一般的に直感的に理解されます( " oh、that "反応を保証)、
  • 上記のメカニズムを必要とするしきい値、および使用する際のシームレス性に影響を与えます。
  • 説明は、より重要な概念から気を散らすものです。

用語

さらなる分類

上記のポリモーフィックメカニズムを考えると、さまざまな方法で分類できます。

  • ポリモーフィック型固有のコードはいつ選択されますか?

    • 実行時は、コンパイラが実行中にプログラムが処理する可能性のあるすべてのタイプのコードを生成する必要があり、実行時に正しいコードが選択されることを意味します(仮想ディスパッチ
    • コンパイル時間は、コンパイル時にタイプ固有のコードを選択することを意味します。この結果:上記のf引数でのみintと呼ばれるプログラム-使用される多態的なメカニズムとインライン化の選択に応じて、コンパイラはf(double)のコードの生成を避けるかもしれません、または生成されたコードは、コンパイルまたはリンクのある時点で破棄される可能性があります。 (仮想ディスパッチを除く上記のすべてのメカニズム)

  • どのタイプがサポートされていますか?

    • Ad-hocは、各タイプをサポートするために明示的なコードを提供することを意味します(オーバーロード、テンプレートの特殊化など)。 「これ」( ad hoc の意味による)タイプ、他の「this」、および「that」もサポートすることを明示的に追加します;-)。
    • Parametricは、それらのサポート(テンプレート、マクロなど)を有効にするために特に何もせずに、さまざまなパラメータータイプに対して関数を使用しようとすることができることを意味します。テンプレート/マクロが期待するように機能する関数/演算子を持つオブジェクト1  is そのテンプレート/マクロがその仕事をするために必要なものはすべて、正確なタイプは無関係です。 C++ 11から切り取られた「概念」は、そのような期待を表現し、強制するのに役立ちます-彼らがそれを後の標準にすることを望みましょう。

      • パラメトリック多型は、カモタイピング-明らかにと言ったジェームズ・ウィットコム・ライリーに起因する概念を提供しますアヒルのようにアヒルと泳ぎ、アヒルのように鳴き、私はその鳥をアヒルと呼びます。」

        template <typename Duck>
        void do_ducky_stuff(const Duck& x) { x.walk().swim().quack(); }
        
        do_ducky_stuff(Vilified_Cygnet());
        
    • サブタイプ(別名包含)ポリモーフィズムアルゴリズム/関数を更新せずに新しいタイプで作業できますが、同じベースクラス(仮想ディスパッチ)

1 -テンプレートは非常に柔軟です。 [〜#〜] sfinae [〜#〜]std::enable_if も参照)は、パラメトリック多相性に対する期待のいくつかのセットを効果的に許可します。たとえば、処理しているデータのタイプに.size()メンバーがある場合、1つの関数を使用し、そうでない場合は.size()を必要としない別の関数をエンコードできます(ただし、おそらく何らかの方法で苦しんでいます-例えば、より遅いstrlen()を使用したり、ログに有用なメッセージを出力しないなど)。テンプレートが特定のパラメーターでインスタンス化され、パラメーターをパラメーターのままにする( partial template specialisation )またはしない(- full specialisation )。

「多態性」

Alf Steinbachは、C++標準polymorphicでは、仮想ディスパッチを使用したランタイムポリモーフィズムのみを参照しているとコメントしています。一般的な比較科学C++の作成者Bjarne Stroustrupの用語集( http://www.stroustrup.com/glossary.html )によると、意味はより包括的です。

ポリモーフィズム-さまざまなタイプのエンティティに単一のインターフェースを提供します。仮想関数は、基本クラスによって提供されるインターフェイスを介して動的(実行時)ポリモーフィズムを提供します。オーバーロードされた関数とテンプレートは、静的(コンパイル時)ポリモーフィズムを提供します。 TC++ PL 12.2.6、13.6.1、D&E 2.9。

この答え-質問のように-C++機能をCompに関連付けます。科学用語。

討論

Compよりも「ポリモーフィズム」の狭い定義を使用するC++標準。科学コミュニティ、 your オーディエンスの相互理解を確実にするために考慮します...

  • 明確な用語を使用する(「このコードを多態的にすることはできますか?」ではなく、「このコードを他のタイプで再利用可能にすることができますか?」または「仮想ディスパッチを使用できますか?」)、および/または
  • 用語を明確に定義します。

それでも、優れたC++プログラマーになるために重要なのは、理解ポリモーフィズムが実際に何をしているかです...

「アルゴリズム」コードを一度書いてから、それを多くのタイプのデータに適用する

...そして、さまざまなポリモーフィックメカニズムが実際のニーズにどのように適合するかを十分に認識してください。

実行時多型スーツ:

  • 入力はファクトリメソッドによって処理され、Base*sを介して処理される異種オブジェクトコレクションとして吐き出されます。
  • 構成ファイル、コマンドラインスイッチ、UI設定などに基づいて実行時に選択される実装
  • 実装は、ステートマシンパターンなど、実行時に変化しました。

ランタイムポリモーフィズム用の明確なドライバーがない場合、多くの場合、コンパイル時オプションが推奨されます。考慮してください:

  • テンプレートクラスのコンパイルと呼ばれる側面は、実行時に失敗するファットインターフェイスよりも望ましい
  • [〜#〜] sfinae [〜#〜]
  • [〜#〜] crtp [〜#〜]
  • 最適化(インライン化とデッドコードの除去、ループの展開、静的スタックベースの配列とヒープを含む多く)
  • __FILE____LINE__、文字列リテラルの連結、およびマクロのその他の固有の機能(悪のままです;-))
  • テンプレートとマクロはセマンティック使用をテストしますが、そのサポートの提供方法を​​人為的に制限しないでください(仮想ディスパッチは厳密に一致するメンバー関数オーバーライドを要求する傾向があるため)

多型をサポートするその他のメカニズム

約束どおり、完全を期すために、いくつかの周辺トピックがカバーされています。

  • コンパイラが提供するオーバーロード
  • 変換
  • キャスト/強制

この答えは、上記を組み合わせてポリモーフィックコード、特にパラメトリックポリモーフィズム(テンプレートとマクロ)を強化および簡素化する方法の説明で終わります。

タイプ固有の操作へのマッピングのメカニズム

>暗黙的なコンパイラー提供のオーバーロード

概念的には、コンパイラは組み込み型の多くの演算子 overloads を使用します。概念的にはユーザー指定のオーバーロードと違いはありませんが、見過ごされやすいためリストされています。たとえば、x += 2と同じ表記を使用してintsとdoublesに追加すると、コンパイラーは以下を生成します。

  • タイプ固有のCPU命令
  • 同じタイプの結果。

その後、オーバーロードはユーザー定義型にシームレスに拡張されます。

std::string x;
int y = 0;

x += 'c';
y += 'c';

コンパイラが提供する基本型のオーバーロードは、高レベル(3GL +)のコンピューター言語では一般的であり、多態性の明示的な議論は一般にもっと何かを意味します。 (2GL-アセンブリ言語-多くの場合、プログラマーは異なるタイプに異なるニーモニックを明示的に使用する必要があります。)

>標準変換

C++標準の4番目のセクションでは、標準変換について説明します。

最初のポイントはうまくまとめています(古いドラフトから-うまくいけば実質的に正しい):

-1-標準変換は、組み込み型に対して定義された暗黙的な変換です。 Clause convは、このような変換の完全なセットを列挙します。標準変換シーケンスは、次の順序での標準変換のシーケンスです。

  • 次のセットからのゼロまたは1つの変換:左辺値から右辺値への変換、配列からポインターへの変換、および関数からポインターへの変換。

  • 次のセットからのゼロまたは1つの変換:整数プロモーション、浮動小数点プロモーション、整数変換、浮動小数点変換、浮動整数変換、ポインター変換、メンバーへのポインター変換、およびブール変換。

  • ゼロまたは1つの資格変換。

[注:標準の変換シーケンスは空にすることができます。つまり、変換なしで構成できます。 ]必要な場合、必要な宛先タイプに変換するために、標準の変換シーケンスが式に適用されます。

これらの変換により、次のようなコードが許可されます。

double a(double x) { return x + 2; }

a(3.14);
a(42);

以前のテストの適用:

多態であるためには、[a()]は少なくとも2つのdistinct型の値(たとえばintdouble)、タイプに適したコードを見つけて実行する

a()自体はdouble専用のコードを実行するため、 not polymorphicです。

ただし、a()の2回目の呼び出しでは、コンパイラは、4242.0に変換するための「浮動小数点プロモーション」(標準§4)の型に適したコードを生成することを知っています。その余分なコードは calling 関数にあります。この重要性については、結論で説明します。

>強制、キャスト、暗黙のコンストラクター

これらのメカニズムにより、ユーザー定義クラスは、組み込み型の標準変換に類似した動作を指定できます。みてみましょう:

int a, b;

if (std::cin >> a >> b)
    f(a, b);

ここでは、オブジェクトstd::cinは、変換演算子の助けを借りて、ブールコンテキストで評価されます。これは、上記のトピックの「標準コンバージョン」の「統合プロモーション」などで概念的にグループ化できます。

暗黙のコンストラクターも事実上同じことを行いますが、キャスト先の型によって制御されます:

f(const std::string& x);
f("hello");  // invokes `std::string::string(const char*)`

コンパイラが提供するオーバーロード、変換、および強制の意味

考慮してください:

void f()
{
    typedef int Amount;
    Amount x = 13;
    x /= 2;
    std::cout << x * 1.1;
}

分割中にxの量を実数として扱う(つまり、6に切り捨てるのではなく6.5にする)場合は、 only typedef double Amountに変更する必要があります。

それは素晴らしいことですが、コードを明示的に「正しい型」にするための too 作業はあまりありませんでした。

void f()                               void f()
{                                      {
    typedef int Amount;                    typedef double Amount;
    Amount x = 13;                         Amount x = 13.0;
    x /= 2;                                x /= 2.0;
    std::cout << double(x) * 1.1;          std::cout << x * 1.1;
}                                      }

しかし、最初のバージョンをtemplateに変換できると考えてください:

template <typename Amount>
void f()
{
    Amount x = 13;
    x /= 2;
    std::cout << x * 1.1;
}

これらの小さな「便利な機能」が原因で、intまたはdoubleのいずれかに対して非常に簡単にインスタンス化でき、意図したとおりに機能します。これらの機能がなければ、次のような明示的なキャスト、型特性、ポリシークラス、または冗長でエラーが発生しやすい混乱が必要になります。

template <typename Amount, typename Policy>
void f()
{
    Amount x = Policy::thirteen;
    x /= static_cast<Amount>(2);
    std::cout << traits<Amount>::to_double(x) * 1.1;
}

そのため、組み込み型、標準変換、キャスト/強制/暗黙的コンストラクターに対するコンパイラー提供の演算子オーバーロード-これらはすべて、多態性の微妙なサポートに貢献します。この回答の冒頭の定義から、彼らはマッピングによって「タイプに合ったコードを見つけて実行する」ことに取り組んでいます:

  • パラメータタイプから「離れた」

    • from 多くのデータ型の多相アルゴリズムのコードハンドル

    • to (潜在的に少ない)数の(同じまたは他の)タイプ用に記述されたコード。

  • 定数型の値からの「to」パラメトリック型

ポリモーフィックコンテキストを not 確立しますが、そのようなコンテキスト内のコードを強化/簡素化します。

あなたはだまされたように感じるかもしれません...それは多くのように思えません。重要なことは、パラメトリックな多相コンテキスト(テンプレートまたはマクロ内)で、任意の広い範囲の型をサポートしようとしているが、多くの場合、他の関数、リテラル、およびタイプの小さなセット。操作/値が論理的に同じ場合、タイプごとにほぼ同一の関数またはデータを作成する必要性を減らします。これらの機能は協力して「ベストエフォート」の姿勢を追加し、利用可能な限られた機能とデータを使用して直感的に期待されることを行い、実際にあいまいな場合にエラーで停止します。

これにより、ポリモーフィックコードをサポートするポリモーフィックコードの必要性を制限し、ポリモーフィズムの使用に関するより緊密なネットを描くことで、ローカライズされた使用が広範に使用されることを強制せず、実装を公開するコストを課すことなく、必要に応じてポリモーフィズムの利点を利用できるようにしますコンパイル時、オブジェクトコードに同じ論理関数の複数のコピーを使用して、使用される型をサポートし、インライン化または少なくともコンパイル時の解決された呼び出しとは対照的に仮想ディスパッチを行います。 C++で一般的であるように、プログラマには、ポリモーフィズムが使用される境界を制御するための多くの自由が与えられます。

212
Tony Delroy

C++での重要な違いは、実行時バインディングとコンパイル時バインディングです。後で説明するように、アドホックとパラメトリックは実際には役立ちません。

|----------------------+--------------|
| Form                 | Resolved at  |
|----------------------+--------------|
| function overloading | compile-time |
| operator overloading | compile-time |
| templates            | compile-time |
| virtual methods      | run-time     |
|----------------------+--------------|

注-ランタイムポリモーフィズムは、コンパイル時に解決される場合がありますが、それは単なる最適化です。実行時の解決を効率的にサポートする必要があり、他の問題とのトレードオフが必要なことは、仮想機能が本来のものであることにつながった理由の一部です。そして、それはC++のすべての形態のポリモーフィズムにとって本当に重要です-それぞれが異なるコンテキストで行われたトレードオフの異なるセットから生じます。

関数のオーバーロードと演算子のオーバーロードは、あらゆる点で重要です。それらを使用するための名前と構文は、ポリモーフィズムに影響しません。

テンプレートを使用すると、多数の関数オーバーロードを一度に指定できます。

同じ解決時のアイデアには別の名前のセットがあります...

|---------------+--------------|
| early binding | compile-time |
| late binding  | run-time     |
|---------------+--------------|

これらの名前はOOPに関連付けられているため、テンプレートまたは他の非メンバー関数が事前バインディングを使用していると言うのは少し奇妙です。

仮想関数と関数のオーバーロードの関係をよりよく理解するには、「単一ディスパッチ」と「複数ディスパッチ」の違いを理解することも役立ちます。アイデアは進歩として理解することができます...

  • まず、単相関数があります。関数の実装は、関数名によって一意に識別されます。特別なパラメーターはありません。
  • 次に、単一のディスパッチがあります。パラメーターの1つは特別と見なされ、使用する実装を識別するために(名前とともに)使用されます。 OOPでは、このパラメーターを「オブジェクト」と見なし、関数名などの前にリストする傾向があります。
  • 次に、複数のディスパッチがあります。任意/すべてのパラメーターは、使用する実装の識別に役立ちます。したがって、ここでもパラメーターを特別にする必要はありません。

OOPには、1つのパラメーターを特別なものとして指定する言い訳以上のものがありますが、それはその一部です。そして、トレードオフについて述べたことに関連して-単一ディスパッチは非常に簡単です効率的に実行します(通常の実装は「仮想テーブル」と呼ばれます)。複数のディスパッチは、効率の点だけでなく、個別のコンパイルの場合にも扱いにくいです。興味がある場合は、「式の問題」を調べてください。

非メンバー関数に「アーリーバインディング」という用語を使用するのが少し奇妙であるように、コンパイル時にポリモーフィズムが解決される「シングルディスパッチ」および「マルチディスパッチ」という用語を使用するのは少し奇妙です。通常、C++は複数のディスパッチを持たないと見なされます。これは特定の種類のランタイム解決と見なされます。ただし、関数のオーバーロードは、コンパイル時に行われる複数のディスパッチとして見ることができます。

パラメトリック対アドホックポリモーフィズムに戻ると、これらの用語は関数型プログラミングでより一般的であり、C++ではまったく機能しません。たとえそうであっても...

パラメトリック多相性とは、パラメータとして型があることを意味し、それらのパラメータに使用する型に関係なく、まったく同じコードが使用されます。

アドホックポリモーフィズムは、特定のタイプに応じて異なるコードを提供するという意味でアドホックです。

オーバーロードと仮想関数は両方とも、アドホックポリモーフィズムの例です。

繰り返しますが、いくつかの同義語があります...

|------------+---------------|
| parametric | unconstrained |
| ad-hoc     | constrained   |
|------------+---------------|

これらはまったく同義語ではありませんが、一般的にはあたかもそうであるかのように扱われますが、C++で混乱が生じる可能性が高いのは例外です。

これらを同義語として扱う背後にある理由は、ポリモーフィズムを特定のタイプのクラスに制限することにより、それらのタイプのクラスに固有の操作を使用できるようになることです。ここでの「クラス」という言葉は、OOPの意味で解釈できますが、実際には特定の操作を共有する(通常は名前が付けられた)タイプのセットを指します。

したがって、制約のない多型を暗示するために、通常、パラメトリック多型が(少なくともデフォルトでは)使用されます。型パラメーターに関係なく同じコードが使用されるため、サポート可能な操作はすべての型で機能する操作のみです。タイプのセットに制約を付けないことにより、それらのタイプに適用できる操作のセットを厳しく制限します。

例えばHaskell、あなたが持つことができる...

myfunc1 :: Bool -> a -> a -> a
myfunc1 c x y = if c then x else y

ここのaは、制約のない多相型です。何でもかまいませんので、その型の値でできることはあまりありません。

myfunc2 :: Num a => a -> a
myfunc2 x = x + 3

ここで、aNumクラス(数値のように振る舞う型)のメンバーに制限されています。この制約により、値を追加するなど、これらの値を使用して数値のような処理を実行できます。 3は多態性です-型推論は、3タイプa

これは、制約付きパラメトリック多型と考えています。実装は1つだけですが、制約のある場合にのみ適用できます。アドホックな側面は、+および3使用します。 Numの各「インスタンス」には、これらの独自の個別の実装があります。 Haskellでさえ、「パラメトリック」と「制約なし」は本当に同義語ではありません。私を責めないでください、それは私のせいではありません!

C++では、オーバーロード関数と仮想関数の両方がアドホックなポリモーフィズムです。アドホックポリモーフィズムの定義では、実行時またはコンパイル時に実装が選択されるかどうかは関係ありません。

C++は、すべてのテンプレートパラメータの型がtypenameである場合、テンプレートを使用したパラメトリック多態性に非常に近づきます。型パラメーターがあり、使用される型に関係なく単一の実装があります。ただし、「置換の失敗はエラーではない」というルールは、テンプレート内で操作を使用した結果として暗黙的な制約が発生することを意味します。追加の複雑化には、代替テンプレート(異なる(アドホック)実装)を提供するためのテンプレートの特殊化が含まれます。

ある意味では、C++にはパラメトリックなポリモーフィズムがありますが、暗黙的に制約されており、アドホックな代替手段によってオーバーライドされる可能性があります。つまり、この分類は実際にはC++では機能しません。

14
Steve314

これは何の助けにもならないかもしれませんが、メイン関数にSTARTENDなどの定義済み関数を与えることで友人にプログラミングを紹介するために作成しました。彼らはmain.cppファイルのみを使用しました)。ポリモーフィッククラスと構造体、テンプレート、ベクトル、配列、プリプロセッサディレクティブ、フレンドシップ、演算子、およびポインター(これらはすべて、ポリモーフィズムを試みる前に知っておくべきものです):

注:完了していませんが、アイデアを得ることができます

main.cpp

#include "main.h"
#define ON_ERROR_CLEAR_SCREEN false
START
    Library MyLibrary;
    Book MyBook("My Book", "Me");
    MyBook.Summarize();
    MyBook += "Hello World";
    MyBook += "HI";
    MyBook.EditAuthor("Joe");
    MyBook.EditName("Hello Book");
    MyBook.Summarize();
    FixedBookCollection<FairyTale> FBooks("Fairytale Books");
    FairyTale MyTale("Tale", "Joe");
    FBooks += MyTale;
    BookCollection E("E");
    MyLibrary += E;
    MyLibrary += FBooks;
    MyLibrary.Summarize();
    MyLibrary -= FBooks;
    MyLibrary.Summarize();
    FixedSizeBookCollection<5> Collection("My Fixed Size Collection");
    /* Extension Work */ Book* Duplicate = MyLibrary.DuplicateBook(&MyBook);
    /* Extension Work */ Duplicate->Summarize();
END

main.h

#include <iostream>
#include <sstream>
#include <vector>
#include <string>
#include <type_traits>
#include <array>
#ifndef __cplusplus
#error Not C++
#endif
#define START int main(void)try{
#define END GET_ENTER_EXIT return(0);}catch(const std::exception& e){if(ON_ERROR_CLEAR_SCREEN){system("cls");}std::cerr << "Error: " << e.what() << std::endl; GET_ENTER_EXIT return (1);}
#define GET_ENTER_EXIT std::cout << "Press enter to exit" << std::endl; getchar();
class Book;
class Library;
typedef std::vector<const Book*> Books;
bool sContains(const std::string s, const char c){
    return (s.find(c) != std::string::npos);
}
bool approve(std::string s){
    return (!sContains(s, '#') && !sContains(s, '%') && !sContains(s, '~'));
}
template <class C> bool isBook(){
    return (typeid(C) == typeid(Book) || std::is_base_of<Book, C>());
}
template<class ClassToDuplicate> class DuplicatableClass{ 
public:
    ClassToDuplicate* Duplicate(ClassToDuplicate ToDuplicate){
        return new ClassToDuplicate(ToDuplicate);
    }
};
class Book : private DuplicatableClass<Book>{
friend class Library;
friend struct BookCollection;
public:
    Book(const char* Name, const char* Author) : name_(Name), author_(Author){}
    void operator+=(const char* Page){
        pages_.Push_back(Page);
    }
    void EditAuthor(const char* AuthorName){
        if(approve(AuthorName)){
            author_ = AuthorName;
        }
        else{
            std::ostringstream errorMessage;
            errorMessage << "The author of the book " << name_ << " could not be changed as it was not approved";
            throw std::exception(errorMessage.str().c_str());
        }
    }
    void EditName(const char* Name){
        if(approve(Name)){
            name_ = Name;
        }
        else{
            std::ostringstream errorMessage;
            errorMessage << "The name of the book " << name_ << " could not be changed as it was not approved";
            throw std::exception(errorMessage.str().c_str());
        }
    }
    virtual void Summarize(){
        std::cout << "Book called " << name_ << "; written by " << author_ << ". Contains "
            << pages_.size() << ((pages_.size() == 1) ? " page:" : ((pages_.size() > 0) ? " pages:" : " pages")) << std::endl;
        if(pages_.size() > 0){
            ListPages(std::cout);
        }
    }
private:
    std::vector<const char*> pages_;
    const char* name_;
    const char* author_;
    void ListPages(std::ostream& output){
        for(int i = 0; i < pages_.size(); ++i){
            output << pages_[i] << std::endl;
        }
    }
};
class FairyTale : public Book{
public:
    FairyTale(const char* Name, const char* Author) : Book(Name, Author){}
};
struct BookCollection{
friend class Library;
    BookCollection(const char* Name) : name_(Name){}
    virtual void operator+=(const Book& Book)try{
        Collection.Push_back(&Book); 
    }catch(const std::exception& e){
        std::ostringstream errorMessage;
        errorMessage << e.what() << " - on line (approx.) " << (__LINE__ -3);
        throw std::exception(errorMessage.str().c_str());
    }
    virtual void operator-=(const Book& Book){
        for(int i = 0; i < Collection.size(); ++i){
            if(Collection[i] == &Book){
                Collection.erase(Collection.begin() + i);
                return;
            }
        }
        std::ostringstream errorMessage;
        errorMessage << "The Book " << Book.name_ << " was not found, and therefore cannot be erased";
        throw std::exception(errorMessage.str().c_str());
    }
private:
    const char* name_;
    Books Collection;
};
template<class FixedType> struct FixedBookCollection : public BookCollection{
    FixedBookCollection(const char* Name) : BookCollection(Name){
        if(!isBook<FixedType>()){
            std::ostringstream errorMessage;
            errorMessage << "The type " << typeid(FixedType).name() << " cannot be initialized as a FixedBookCollection";
            throw std::exception(errorMessage.str().c_str());
            delete this;
        }
    }
    void operator+=(const FixedType& Book)try{
        Collection.Push_back(&Book); 
    }catch(const std::exception& e){
        std::ostringstream errorMessage;
        errorMessage << e.what() << " - on line (approx.) " << (__LINE__ -3);
        throw std::exception(errorMessage.str().c_str());
    }
    void operator-=(const FixedType& Book){
        for(int i = 0; i < Collection.size(); ++i){
            if(Collection[i] == &Book){
                Collection.erase(Collection.begin() + i);
                return;
            }
        }
        std::ostringstream errorMessage;
        errorMessage << "The Book " << Book.name_ << " was not found, and therefore cannot be erased";
        throw std::exception(errorMessage.str().c_str());
    }
private:
    std::vector<const FixedType*> Collection;
};
template<size_t Size> struct FixedSizeBookCollection : private std::array<const Book*, Size>{
    FixedSizeBookCollection(const char* Name) : name_(Name){ if(Size < 1){ throw std::exception("A fixed size book collection cannot be smaller than 1"); currentPos = 0; } }
    void operator+=(const Book& Book)try{
        if(currentPos + 1 > Size){
            std::ostringstream errorMessage;
            errorMessage << "The FixedSizeBookCollection " << name_ << "'s size capacity has been overfilled";
            throw std::exception(errorMessage.str().c_str());
        }
        this->at(currentPos++) = &Book;
    }catch(const std::exception& e){
        std::ostringstream errorMessage;
        errorMessage << e.what() << " - on line (approx.) " << (__LINE__ -3);
        throw std::exception(errorMessage.str().c_str());
    }
private:
    const char* name_;
    int currentPos;
};
class Library : private std::vector<const BookCollection*>{
public:
    void operator+=(const BookCollection& Collection){
        for(int i = 0; i < size(); ++i){
            if((*this)[i] == &Collection){
                std::ostringstream errorMessage;
                errorMessage << "The BookCollection " << Collection.name_ << " was already in the library, and therefore cannot be added";
                throw std::exception(errorMessage.str().c_str());
            }
        }
        Push_back(&Collection);
    }
    void operator-=(const BookCollection& Collection){
        for(int i = 0; i < size(); ++i){
            if((*this)[i] == &Collection){
                erase(begin() + i);
                return;
            }
        }
        std::ostringstream errorMessage;
        errorMessage << "The BookCollection " << Collection.name_ << " was not found, and therefore cannot be erased";
        throw std::exception(errorMessage.str().c_str());
    }
    Book* DuplicateBook(Book* Book)const{
        return (Book->Duplicate(*Book));
    }
    void Summarize(){
        std::cout << "Library, containing " << size() << ((size() == 1) ? " book collection:" : ((size() > 0) ? " book collections:" : " book collections")) << std::endl;
        if(size() > 0){
            for(int i = 0; i < size(); ++i){
                std::cout << (*this)[i]->name_ << std::endl;
            }
        }
    }
};
2
Joe

アドホックポリモーフィズムに関しては、関数のオーバーロードまたは演算子のオーバーロードを意味します。こちらをご覧ください:

http://en.wikipedia.org/wiki/Ad-hoc_polymorphism

パラメトリック多相性に関しては、テンプレート関数は、FIXED型のパラメーターを必ずしも取り入れないため、カウントすることもできます。たとえば、1つの関数は整数の配列をソートでき、文字列の配列もソートできます。

http://en.wikipedia.org/wiki/Parametric_polymorphism

2
Eric Z

多相クラスを使用した基本的な例を次に示します

#include <iostream>

class Animal{
public:
   Animal(const char* Name) : name_(Name){/* Add any method you would like to perform here*/
    virtual void Speak(){
        std::cout << "I am an animal called " << name_ << std::endl;
    }
    const char* name_;
};

class Dog : public Animal{
public:
    Dog(const char* Name) : Animal(Name) {/*...*/}
    void Speak(){
        std::cout << "I am a dog called " << name_ << std::endl;
    }
};

int main(void){
    Animal Bob("Bob");
    Dog Steve("Steve");
    Bob.Speak();
    Steve.Speak();
    //return (0);
}
1
user2976089

ポリモーフィズムとは、オペレータが異なるインスタンスで異なる動作をするために使用される多くの形態を意味します。ポリモーフィズムは、継承を実装するために使用されます。たとえば、クラスシェイプのfn draw()を定義した後、円、ボックス、三角形、その他のシェイプを描画するためにドローfnを実装できます。 (クラス形状のオブジェクトです)