133

私の知る限り:

C ++は、3つの異なるタイプのポリモーフィズムを提供します。

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

上記の3種類のポリモーフィズムに加えて、他の種類のポリモーフィズムが存在します。

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

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

しかし、他の2つについては

アドホック多相:

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

パラメトリック多型:

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

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

可能であれば、例を挙げて両方を説明できますか?この質問への回答が、大学からの多くの新しいパスアウトに役立つことを願っています。

4

7 に答える 7

224

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

多型を理解するには(この用語はコンピューティングサイエンスで使用されているため)、多型の簡単なテストと定義から始めると役立ちます。検討:

    Type1 x;
    Type2 y;

    f(x);
    f(y);

ここでf()は、いくつかの操作を実行し、値xy入力として与えられています。

ポリモーフィズムを示すf()には、少なくとも2つの異なるタイプ(例intdouble)の値を操作して、異なるタイプに適したコードを見つけて実行できる必要があります。


ポリモーフィズムの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
    

その他の関連メカニズム

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

  • とにかく、彼らは一般的に直感的に理解されています(「ああ、その」反応を保証します)、
  • これらは、上記のメカニズムを要求する際のしきい値と使用のシームレスさに影響を与えます。
  • 説明は、より重要な概念からの厄介な気晴らしです。

用語

さらなる分類

上記の多型メカニズムを考えると、さまざまな方法でそれらを分類できます。

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

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

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

    • アドホックとは、各タイプ(オーバーロード、テンプレートの特殊化など)をサポートする明示的なコードを提供することを意味します。「これに対する」(アドホックの意味による)タイプ、他の「これ」、そしておそらく「それ」もサポートを明示的に追加します;-)。
    • パラメトリックとは、さまざまなパラメータータイプ(テンプレート、マクロなど)のサポートを有効にするために特別なことを何もせずに、さまざまなパラメータータイプに対して関数を使用できることを意味します。テンプレート/マクロが1 期待するように動作する関数/演算子を持つオブジェクトは、テンプレート/マクロがその仕事をするために必要なすべてであり、正確なタイプは関係ありません。C ++ 20によって導入された「概念」は、そのような期待を表現および実施します。ここのcppreferenceページを参照してください。

      • パラメトリック多型は、ダックタイピングを提供します。これは、「アヒルのように歩き、アヒルのように泳ぎ、アヒルのように鳴く鳥を見ると、その鳥をアヒルと呼ぶ」と明らかに言ったジェームズウィットコムライリーに起因する概念です。。

        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()メンバーがある場合は、ある関数を使用するようにエンコードすることができます。それ以外の場合は、必要のない別の関数を使用します.size()(ただし、おそらく何らかの形で問題が発生しstrlen()ます。ログ内の有用なメッセージ)。テンプレートが特定のパラメーターでインスタンス化されるときのアドホックな動作を指定することもできます。一部のパラメーターはパラメトリックのままにするか(部分的なテンプレートの特殊化)、そうでないか(完全な特殊化)です。

「多形」

Alf Steinbachは、C ++標準のポリモーフィックでは、仮想ディスパッチを使用した実行時のポリモーフィズムのみを参照しているとコメントしています。ジェネラルコンプ 科学 C++の作成者であるBjarneStroustrupの用語集(http://www.stroustrup.com/glossary.html)によると、意味はより包括的です。

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

この回答は、質問と同様に、C++の機能をCompに関連付けています。科学 用語。

討論

Compよりも狭い「ポリモーフィズム」の定義を使用するC++標準を使用します。科学 コミュニティ、あなたの聴衆のための相互理解を確実にするために考慮してください...

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

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

    「アルゴリズム」コードを一度記述して、それをさまざまな種類のデータに適用できるようにします

...そして、さまざまな多形メカニズムが実際のニーズにどのように一致するかをよく理解してください。

実行時のポリモーフィズムは次のように適合します。

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

実行時のポリモーフィズムの明確な推進要因がない場合は、コンパイル時のオプションが望ましいことがよくあります。検討:

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

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

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

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

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

タイプ固有の操作にマッピングするためのメカニズム

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

概念的には、コンパイラーは組み込み型の多くの演算子をオーバーロードします。これは、ユーザー指定のオーバーロードと概念的には異なりませんが、見落とされがちなためリストされています。たとえば、同じ表記を使用してintsとsに追加すると、コンパイラは次のように生成します。doublex += 2

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

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

std::string x;
int y = 0;

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

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

>標準変換

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

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

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

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

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

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

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

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

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

a(3.14);
a(42);

以前のテストの適用:

多態的であるためには、[ a()]は少なくとも2つの異なる型(egintdouble)の値で動作し、型に適したコードを見つけて実行できる必要があります。

a()それ自体が専用のコードを実行するため、多態性doubleではありません

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

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

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

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にする場合)、に変更するだけでtypedef double Amount済みます。

それは素晴らしいことですが、コードを明示的に「タイプを正しく」するのにそれほど手間はかかりませんでした。

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;
}

したがって、コンパイラが提供する組み込み型の演算子のオーバーロード、標準変換、キャスト/強制/暗黙のコンストラクタ-これらはすべて、ポリモーフィズムの微妙なサポートに貢献します。この回答の上部にある定義から、彼らはマッピングによって「タイプに適したコードを見つけて実行する」ことに取り組んでいます。

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

    • 多くのデータ型から多形アルゴリズムコードが処理します

    • (同じまたは他の)タイプの(潜在的に少ない)数のために書かれたコードへ。

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

それらはそれ自体でポリモーフィックコンテキストを確立しませんが、そのようなコンテキスト内のコードを強化/簡素化するのに役立ちます。

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

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

于 2011-05-02T08:11:31.490 に答える
15

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

ここでaは、クラスのメンバーになるように制約されていますNum-数値のように機能するタイプ。この制約により、それらの値を追加するなど、それらの値を使用して数の多いことを行うことができます。も3ポリモーフィックです-型推論は、あなた3が型のを意味していることを理解しますa

私はこれを制約されたパラメトリック多型と考えています。実装は1つだけですが、制約のある場合にのみ適用できます。アドホックな側面は、どちらを使用するかを選択+する3ことです。の各「インスタンス」にNumは、これらの独自の実装があります。したがって、Haskellでも、「パラメトリック」と「制約なし」は実際には同義語ではありません。私を責めないでください。私のせいではありません。

C ++では、オーバーロード関数と仮想関数の両方がアドホック多相です。アドホック多相性の定義では、実装が実行時に選択されるかコンパイル時に選択されるかは関係ありません。

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

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

于 2013-11-17T08:15:41.720 に答える
2

アドホック多相性に関しては、関数のオーバーロードまたは演算子のオーバーロードを意味します。ここをチェックしてください:

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

パラメトリックポリモーフィズムに関しては、テンプレート関数もFIXEDタイプのパラメーターを必ずしも取り込むとは限らないため、カウントすることができます。たとえば、1つの関数は整数の配列を並べ替えることができ、文字列の配列なども並べ替えることができます。

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

于 2011-05-02T07:44:35.723 に答える
2

これは役に立たないかもしれませんが、私は友人にプログラミングを紹介するために、などの定義された関数を提供することSTARTで作成しました。ENDまた、main関数については、それほど気が遠くなることはありませんでした(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;
            }
        }
    }
};
于 2013-12-27T18:45:40.120 に答える
1

ポリモーフィッククラスを使用した基本的な例を次に示します。

#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);
}
于 2013-12-26T09:43:06.857 に答える
0

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

于 2016-08-25T21:43:19.757 に答える
-3

誰かがこれらの人々にCUTと言うなら

The Surgeon
The Hair Stylist
The Actor

何が起こるか?

The Surgeon would begin to make an incision.
The Hair Stylist would begin to cut someone's hair.
The Actor would abruptly stop acting out of the current scene, awaiting directorial guidance.

したがって、上記の表現は、OOPのポリモーフィズム(同じ名前、異なる動作)とは何かを示しています。

あなたが面接に行く予定で、面接官が私たちが座っているのと同じ部屋での多型の実例を教えて/見せるように頼んだら、例えば-

回答-ドア/窓

どのように疑問に思いますか?

ドア/窓から-人が来ることができる、空気が来ることができる、光が来ることができる、雨が来ることができる、など。

つまり、1つの形式が異なる動作(ポリモーフィズム)です。

それをよりよく理解するために、簡単な方法で上記の例を使用しました。コードのリファレンスが必要な場合は、上記の回答に従ってください。

于 2014-11-15T11:55:00.667 に答える