C ++でのポリモーフィズム


129

私の知る限り:

C ++には、3つの異なるタイプのポリモーフィズムがあります。

  • 仮想機能
  • 関数名のオーバーロード
  • オペレーターの過負荷

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

  • ランタイム
  • コンパイル時
  • アドホックポリモーフィズム
  • パラメトリック多態性

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

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

アドホックなポリモーフィズム:

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

パラメトリック多態性:

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

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

可能であれば、誰もが例でそれらの両方を説明できますか?この質問に対する回答が、大学の多くの新入生のために役立つことを願っています。


30
実際、C ++には4種類のポリモーフィズムがあります。パラメトリック(C ++のテンプレートを介したジェネリシティ)、包含(C ++の仮想メソッドを介したサブタイピング)、オーバーロード、強制(暗黙の変換)です。概念的には、関数のオーバーロードと演算子のオーバーロードの違いはほとんどありません。
fredoverflow

だから私が言及したウェブサイトは多くの人を誤解させているようです。
ビジェイ

@zombie:そのウェブサイトは多くの優れた概念に触れていますが、用語の使用において正確で一貫性がありません(たとえば、仮想ディスパッチ/ランタイムのポリモーフィズムについて話し始めると、それは間違っているポリモーフィズムについて多くの声明を出します)一般的には仮想ディスパッチに当てはまります)。すでに主題を理解していれば、あなたは....言われているものに関連し、精神的に必要な注意事項を挿入しますが、サイトを読むことによって、そこに到達するのは難しいことができます
トニー・デルロイ

一部の用語は同義語であるか、他の用語に関連していますが、他の用語よりも制限されています。たとえば、「アドホック多態性」という用語は、私の経験では主にHaskellで使用されていますが、「仮想関数」は非常に密接に関連しています。小さな違いは、「仮想関数」は「遅延バインディング」を使用したメンバー関数を指すオブジェクト指向の用語であるということです。「マルチディスパッチ」も一種のアドホックポリモーフィズムです。そして、FredOverflowが言うように、演算子と関数のオーバーロードは基本的に同じものです。
Steve314

書式を修正しました。編集ペインの右側にあるヘルプをお読みください。200以上の質問と3kを超える人は、この基本的なことを知っているはずです。また、新しいキーボードを購入することもできます。このシフトキーは断続的に失敗しているようです。そして、C ++には「テンプレート関数」などはありません。ただし、関数テンプレートがあります。
sbi

回答:


219

多型の理解/要件

ポリモーフィズムを理解するには(コンピューティングサイエンスでこの用語が使用されているため)、単純なテストとその定義から始めることが役立ちます。考慮してください:

    Type1 x;
    Type2 y;

    f(x);
    f(y);

ここで、f()いくつかの動作を実行することで、値が与えられているxと、y入力として。

ポリモーフィズムを示すにf()は、少なくとも2つの異なる型(例: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
    

その他の関連メカニズム

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

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

用語

さらなる分類

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

  • 多相型固有のコードはいつ選択されますか?

    • 実行時とは、コンパイラーが実行中にプログラムが処理する可能性のあるすべてのタイプのコードを生成する必要があり、実行時に正しいコードが選択される(仮想ディスパッチ
    • コンパイル時とは、コンパイル中に型固有のコードを選択することを意味します。この結果:引数fint指定して上記でのみ呼び出されたプログラムを言う-使用される多態性メカニズムとインライン化の選択に応じて、コンパイラーはのコードの生成を回避するか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 ++の作成者であるBjarne Stroustrupの用語集(http://www.stroustrup.com/glossary.html)のとおり、意味はより包括的です。

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

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

討論

C ++標準では、「多態性」の定義はコンプよりも狭くなっています。サイエンス。コミュニティは、のために相互理解を確実にするために、あなたの聴衆を検討します...

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

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

    「アルゴリズム」コードを一度記述してから、それを多くのタイプのデータに適用できるようにする

...そして、さまざまな多態性メカニズムが実際のニーズにどのように一致するかを十分に認識します

ランタイムポリモーフィズムスーツ:

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

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

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

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

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

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

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

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

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

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

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

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

std::string x;
int y = 0;

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

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

>標準変換

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

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

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

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

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

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

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

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

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

a(3.14);
a(42);

以前のテストを適用する:

ポリモーフィックであるためには、[ a()]は少なくとも2つの異なる型(intおよびdouble)の値を操作でき、型に適したコードを見つけて実行できる必要があります

a()それ自体は特にコードを実行するdoubleため多態的ではありません

ただし、a()コンパイラへの2回目の呼び出しでは、「浮動小数点昇格」(標準§4)の型に適したコードを生成してに変換することがわかっ42てい42.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;
}

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

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

    • より多くのデータ・タイプの多型アルゴリズムコードハンドル

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

  • 定数型の値からパラメトリック型へ

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

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

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


1
-1用語の説明を除いて、すばらしい答えです。C ++標準で、§1.8/ 1で「ポリモーフィック」という用語を定義しています。仮想関数に関するセクション10.3を参照しています。そのため、小刻みな余地、議論の余地、個人的な意見の余地はありません。標準のC ++のコンテキストでは、その用語は一度だけ定義されます。また、実際の役割も果たします。たとえば、§5.2.7/ 6 aboutにdynamic_castは、「多相型のポインタまたは左辺値」へのポインタが必要です。乾杯&hth。、
乾杯とhth。-アルフ

@アルフ:すばらしい参照-あなたの視点は狭すぎると思いますが 過負荷、アドホック、パラメトリック多態性などの質問リストから、答えはC ++の機能を一般的なCompに関連付ける必要があることは非常に明らかです。サイエンス。用語の意味。実際、Stroustrupの用語集では、「ポリモーフィズム-異なるタイプのエンティティへの単一のインターフェースを提供します。仮想関数は、基本クラスによって提供されるインターフェースを通じて動的(ランタイム)ポリモーフィズムを提供します。オーバーロードされた関数とテンプレートは、静的(コンパイル時)ポリモーフィズムを提供します。 TC ++ PL 12.2.6、13.6.1、D&E 2.9。」
Tony Delroy、2011年

@トニー:それはあなたの答えの主な目的が間違っているのではありません。それは大丈夫です、それは素晴らしいです。それはまさにそのことです。あなたが逆に得た用語:正式な学術用語は、ホーリー国際標準によって定義された狭い用語であり、人々がわずかに異なることを意味する非公式の大まかな用語は、主にこの質問と回答で使用される用語です。乾杯&hth。、
乾杯とhth。-アルフ

@アルフ:答えが素晴らしかった-「その他のメカニズム」は5行目で書き直す必要があり、私はより具体的な機能と多態性メカニズムの対照的な意味を熟考/作成しています。とにかく、私の理解は、正式な学術的独占的なC ++に焦点を当てた意味は狭いかもしれないが、正式な学術一般的なコンプです。サイエンス。Stroustrupの用語集で明らかなように、意味はありません。決定的なものが必要です-例えば、Knuthからの定義-まだ運がググリングしていない。私はあなたがC ++の第一人者であることに感謝しますが、これに関する具体的な証拠を具体的に指摘できますか?
Tony Delroy、2011年

1
@アルフ:第二に、ポリモーフィズムはまともな一般的なコンプで正式に定義されていると確信しています。サイエンス。私の使用法(およびStroustrupの使用法)と互換性のある(時代を超えない、安定した)方法で予約します。Wikipediaの記事は、それをそのように定義するいくつかの学術出版物にリンクしています。(lucacardelli.name/Papers/OnUnderstanding.A4.pdfから)。それで、問題は「Comp。Sciの代弁者」です...?
Tony Delroy、2011年

15

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

|----------------------+--------------|
| 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 ++では、オーバーロードと仮想関数の両方がその場限りの多態性です。アドホックポリモーフィズムの定義では、実装が実行時に選択されるか、コンパイル時に選択されるかは関係ありません。

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

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


+1多くの興味深いポイントと洞察。私はHaskellについて数時間しか読んaでいないので、「ここに制約のない多相型[...]があるので、その型の値でできることはあまりありません。」興味がありました-C ++ sans Conceptsでは、テンプレートパラメーターとして指定された型の引数に対して特定の操作セットを試行するだけに制限されていません... boost概念のようなライブラリは他の方法で機能します-型が操作をサポートしていることを確認してください追加の操作の偶発的な使用を防ぐのではなく、指定します。
Tony Delroy、2013年

@Tony-概念は、テンプレートの多態性を明示的に制約する方法です。暗黙的な制約は互換性があるために消えることはありませんが、明示的な制約は明らかに大幅に改善されます。概念の過去の計画の一部はHaskellの型クラスにいくらか関連していたと思いますが、深くは調べませんでした。最後に「浅く」見たとき、Haskellについてはあまり知りませんでした。
Steve314 2013年

「暗黙的な制約は互換性のために明らかに消えない」-メモリから、C ++ 0xコンセプトは(暗黙的に:-/)約束して「暗黙的な制約」を防止しました-コンセプトで約束された方法でのみタイプを使用できました
Tony Delroy、2013年

2

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

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

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

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


1
残念ながら、これは正しいですが、誤解を招く可能性があります。SFINAEルールにより、テンプレート関数は暗黙的な制約を取得できます-テンプレート内の操作を使用すると、暗黙的に多態性が制約されます-テンプレートの特殊化により、より一般的なテンプレートをオーバーライドするアドホックな代替テンプレートを提供できます。したがって、テンプレートは(デフォルトで)制約のないパラメトリック多態性を提供しますが、それを強制することはありません-制約付きまたはアドホックになる可能性がある方法が少なくとも2つあります。
Steve314 2013年

実際、あなたの例-ソート-は制約を意味します。並べ替えは、順序付けされた型(つまり、<および類似の演算子を提供する)に対してのみ機能します。Haskellでは、クラスを使用してその要件を明示的に表現しますOrd<特定のタイプ(のインスタンスによって提供されるOrd)に応じて異なる結果が得られるという事実は、その場限りの多態性と見なされます。
Steve314 2013年

2

これは、任意の助けになることがないかもしれませんが、私は、これはのように、定義された機能を与えることによって、プログラミングに私の友人を紹介して作られたSTART、とEND(彼らはのみ使用される主な機能のために、それはあまりにも気力をくじくていなかったので、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;
            }
        }
    }
};

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

0

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


-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つのフォームの異なる動作(ポリモーフィズム)。

それをよりよく理解するために、私は上記の例を使用しました。コードの参照が必要な場合は、上記の回答に従ってください。


c ++でのポリモーフィズムの理解を深めるために述べたように、上記の例を使用しました。これは、初心者が面接での実行中に、コードの背後で何が意味されているのか、何が起こっているのかを実際に理解して関連付けるのに役立ちます。ありがとうございました!
Sanchit 2014年

opは「C ++でのポリモーフィズム」を尋ねました。あなたの答えはあまりにも抽象的です。
StahlRat 2016
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.