Polymorphism in C++ Ask Question

Polymorphism in C++ Ask Question

AFAIK:

C++ provides three different types of polymorphism.

  • Virtual functions
  • Function name overloading
  • Operator overloading

In addition to the above three types of polymorphism, there exist other kinds of polymorphism:

  • run-time
  • compile-time
  • ad-hoc polymorphism
  • parametric polymorphism

I know that runtime polymorphism can be achieved by virtual functions and static polymorphism can be achieved by template functions

But for the other two

  • ad-hoc polymorphism
  • parametric polymorphism the website says,

ad-hoc polymorphism:

If the range of actual types that can be used is finite and the combinations must be individually specified prior to use, this is called ad-hoc polymorphism.

parametric polymorphism:

If all code is written without mention of any specific type and thus can be used transparently with any number of new types it is called parametric polymorphism.

I can hardly understand them :(

can anyone explain them both if possible with an example? I hope the answers to this questions would be helpful for many new passouts from their colleges.

ベストアンサー1

Understanding of / requirements for polymorphism

To understand polymorphism - as the term is used in Computing Science - it helps to start from a simple test for and definition of it. Consider:

    Type1 x;
    Type2 y;

    f(x);
    f(y);

Here, f() is to perform some operation and is being given values x and y as inputs.

To exhibit polymorphism, f() must be able to operate with values of at least two distinct types (e.g. int and double), finding and executing distinct type-appropriate code.


C++ mechanisms for polymorphism

Explicit programmer-specified polymorphism

You can write f() such that it can operate on multiple types in any of the following ways:

  • Preprocessing:

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

    void f(int& x)    { x += 2; }
    
    void f(double& x) { x += 2; }
    
  • Templates:

    template <typename T>
    void f(T& x) { x += 2; }
    
  • Virtual dispatch:

    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
    

Other related mechanisms

Compiler-provided polymorphism for builtin types, Standard conversions, and casting/coercion are discussed later for completeness as:

  • they're commonly intuitively understood anyway (warranting a "oh, that" reaction),
  • they impact the threshold in requiring, and seamlessness in using, the above mechanisms, and
  • explanation is a fiddly distraction from more important concepts.

Terminology

Further categorisation

Given the polymorphic mechanisms above, we can categorise them in various ways:

  • ポリモーフィック型固有のコードはいつ選択されるのでしょうか?

    • 実行時間コンパイラはプログラムの実行中に処理する可能性のあるすべての型のコードを生成する必要があり、実行時に正しいコードが選択されることを意味します(仮想ディスパッチ
    • コンパイル時間は、コンパイル時に型固有のコードが選択されることを意味します。この結果、たとえば、引数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 - テンプレートは非常に柔軟です。スフィナエ(参照std::enable_if) は、パラメトリック多態性に対する複数の期待セットを効果的に許可します。たとえば、処理するデータの型にメンバーがある場合は.size()1 つの関数を使用し、それ以外の場合は必要のない別の関数.size()(ただし、おそらく何らかの影響を受ける - たとえば、より遅いものを使用したりstrlen()、ログにあまり役に立たないメッセージを出力したりします) をエンコードできます。また、テンプレートが特定のパラメーターでインスタンス化されるときに、一部のパラメーターをパラメトリックのままにするか (部分的なテンプレートの特殊化) か否か (完全な専門化)。

「多形性」

アルフ・スタインバッハは、C++標準では多形性仮想ディスパッチを使用した実行時ポリモーフィズムのみを指します。C++ の作成者 Bjarne Stroustrup の用語集によると、一般的なコンピュータサイエンスの意味はより包括的です (http://www.stroustrup.com/glossary.html (英語)):

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

この回答は、質問と同様に、C++ の機能をコンピューター サイエンスの用語に関連付けます。

議論

C++標準では、コンピュータサイエンスコミュニティよりも狭い「ポリモーフィズム」の定義を使用して、相互理解を確保しています。あなたの視聴者は考慮します...

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

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

    「アルゴリズム」コードを一度書いて、それを多くの種類のデータに適用できるようにする

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

実行時ポリモーフィズムは以下に適しています:

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

実行時のポリモーフィズムを明確に駆動するものがない場合は、コンパイル時のオプションが望ましい場合が多くあります。次の点を考慮してください。

  • テンプレート化されたクラスの「コンパイルされた内容」という側面は、実行時に失敗するファットインターフェースよりも好ましい。
  • スフィナエ
  • CRTP
  • 最適化(インライン化、デッドコードの除去、ループの展開、静的スタックベース配列とヒープの比較など)
  • __FILE____LINE__文字列リテラルの連結、その他のマクロ独自の機能(これらは依然として悪です ;-))
  • テンプレートとマクロはセマンティックな使用法がサポートされているかどうかをテストしますが、そのサポートの提供方法を​​人為的に制限しません(仮想ディスパッチでは、正確に一致するメンバー関数のオーバーライドを要求する傾向があります)。

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

約束どおり、完全性を期すために、いくつかの周辺トピックを取り上げます。

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

この回答は、上記の組み合わせによってポリモーフィック コード、特にパラメトリック ポリモーフィズム (テンプレートとマクロ) が強化され、簡素化される仕組みについて説明して終わります。

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

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

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

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

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

std::string x;
int y = 0;

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

Compiler-provided overloads for basic types is common in high-level (3GL+) computer languages, and explicit discussion of polymorphism generally implies something more. (2GLs - assembly languages - often require the programmer to explicitly use different mnemonics for different types.)

> Standard conversions

The C++ Standard's fourth section describes Standard conversions.

The first point summarises nicely (from an old draft - hopefully still substantially correct):

-1- Standard conversions are implicit conversions defined for built-in types. Clause conv enumerates the full set of such conversions. A standard conversion sequence is a sequence of standard conversions in the following order:

  • Zero or one conversion from the following set: lvalue-to-rvalue conversion, array-to-pointer conversion, and function-to-pointer conversion.

  • Zero or one conversion from the following set: integral promotions, floating point promotion, integral conversions, floating point conversions, floating-integral conversions, pointer conversions, pointer to member conversions, and boolean conversions.

  • Zero or one qualification conversion.

[Note: a standard conversion sequence can be empty, i.e., it can consist of no conversions. ] A standard conversion sequence will be applied to an expression if necessary to convert it to a required destination type.

These conversions allow code such as:

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

a(3.14);
a(42);

Applying the earlier test:

To be polymorphic, [a()] must be able to operate with values of at least two distinct types (e.g. int and double), finding and executing type-appropriate code.

a() itself runs code specifically for double and is therefore not polymorphic.

But, in the second call to a() the compiler knows to generate type-appropriate code for a "floating point promotion" (Standard §4) to convert 42 to 42.0. That extra code is in the calling function. We'll discuss the significance of this in the conclusion.

> Coercion, casts, implicit constructors

These mechanisms allow user-defined classes to specify behaviours akin to builtin types' Standard conversions. Let's have a look:

int a, b;

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

Here, the object std::cin is evaluated in a boolean context, with the help of a conversion operator. This can be conceptually grouped with "integral promotions" et al from the Standard conversions in the topic above.

Implicit constructors effectively do the same thing, but are controlled by the cast-to type:

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

Implications of compiler-provided overloads, conversions and coercion

Consider:

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

If we want the amount x to be treated as a real number during the division (i.e. be 6.5 rather than rounded down to 6), we only need change to typedef double Amount.

That's nice, but it wouldn't have been too much work to make the code explicitly "type correct":

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

But, consider that we can transform the first version into a template:

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

It's due to those little "convenience features" that it can be so easily instantiated for either int or double and work as intended. Without these features, we'd need explicit casts, type traits and/or policy classes, some verbose, error-prone mess like:

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

So, compiler-provided operator overloading for builtin types, Standard conversions, casting / coercion / implicit constructors - they all contribute subtle support for polymorphism. From the definition at the top of this answer, they address "finding and executing type-appropriate code" by mapping:

  • "away" from parameter types

    • from the many data types polymorphic algorithmic code handles

    • to code written for a (potentially lesser) number of (the same or other) types.

  • "to" parametric types from values of constant type

They do not establish polymorphic contexts by themselves, but do help empower/simplify code inside such contexts.

You may feel cheated... it doesn't seem like much. The significance is that in parametric polymorphic contexts (i.e. inside templates or macros), we're trying to support an arbitrarily large range of types but often want to express operations on them in terms of other functions, literals and operations that were designed for a small set of types. It reduces the need to create near-identical functions or data on a per-type basis when the operation/value is logically the same. These features cooperate to add an attitude of "best effort", doing what's intuitively expected by using the limited available functions and data and only stopping with an error when there's real ambiguity.

This helps limit the need for polymorphic code supporting polymorphic code, drawing a tighter net around the use of polymorphism so localised use doesn't force widespread use, and making the benefits of polymorphism available as needed without imposing the costs of having to expose implementation at compile time, have multiple copies of the same logical function in the object code to support the used types, and in doing virtual dispatch as opposed to inlining or at least compile-time resolved calls. As is typical in C++, the programmer is given a lot of freedom to control the boundaries within which polymorphism is used.

おすすめ記事