ポリモーフィズムの理解/要件
多型を理解するには(この用語はコンピューティングサイエンスで使用されているため)、多型の簡単なテストと定義から始めると役立ちます。検討:
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
その他の関連メカニズム
組み込み型、標準変換、およびキャスト/強制のためのコンパイラー提供のポリモーフィズムについては、完全を期すために後で次のように説明します。
- とにかく、彼らは一般的に直感的に理解されています(「ああ、その」反応を保証します)、
- これらは、上記のメカニズムを要求する際のしきい値と使用のシームレスさに影響を与えます。
- 説明は、より重要な概念からの厄介な気晴らしです。
用語
さらなる分類
上記の多型メカニズムを考えると、さまざまな方法でそれらを分類できます。
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__
文字列リテラルの連結およびマクロの他の固有の機能(悪のままです;-))
- テンプレートとマクロはセマンティックの使用法をテストしますが、そのサポートの提供方法を人為的に制限しないでください(仮想ディスパッチは、完全に一致するメンバー関数のオーバーライドを要求する傾向があるため)
多型をサポートする他のメカニズム
約束どおり、完全を期すために、いくつかの周辺トピックがカバーされています。
- コンパイラが提供するオーバーロード
- 変換
- キャスト/強制
この回答は、上記を組み合わせてポリモーフィックコード、特にパラメトリックポリモーフィズム(テンプレートとマクロ)を強化および簡素化する方法についての説明で締めくくられています。
タイプ固有の操作にマッピングするためのメカニズム
>暗黙のコンパイラ提供のオーバーロード
概念的には、コンパイラーは組み込み型の多くの演算子をオーバーロードします。これは、ユーザー指定のオーバーロードと概念的には異なりませんが、見落とされがちなためリストされています。たとえば、同じ表記を使用してint
sとsに追加すると、コンパイラは次のように生成します。double
x += 2
その後、オーバーロードはユーザー定義の型にシームレスに拡張されます。
std::string x;
int y = 0;
x += 'c';
y += 'c';
コンパイラーが提供する基本型のオーバーロードは、高レベル(3GL +)コンピューター言語では一般的であり、ポリモーフィズムの明示的な説明は、一般に、さらに何かを意味します。(2GL(アセンブリ言語)では、多くの場合、プログラマーはタイプごとに異なるニーモニックを明示的に使用する必要があります。)
>標準変換
C ++標準の4番目のセクションでは、標準変換について説明します。
最初のポイントはうまく要約されています(古いドラフトから-うまくいけばまだ実質的に正しい):
-1-標準変換は、組み込み型に対して定義された暗黙的な変換です。節convは、そのような変換の完全なセットを列挙します。標準変換シーケンスは、次の順序での標準変換のシーケンスです。
[注:標準の変換シーケンスは空にすることができます。つまり、変換なしで構成することができます。]必要に応じて、式を必要な宛先タイプに変換するために、標準の変換シーケンスが式に適用されます。
これらの変換により、次のようなコードが可能になります。
double a(double x) { return x + 2; }
a(3.14);
a(42);
以前のテストの適用:
多態的であるためには、[ a()
]は少なくとも2つの異なる型(egint
とdouble
)の値で動作し、型に適したコードを見つけて実行できる必要があります。
a()
それ自体が専用のコードを実行するため、多態性double
ではありません。
しかし、コンパイラへの2番目の呼び出しでは、に変換するa()
「浮動小数点昇格」(標準§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;
}
したがって、コンパイラが提供する組み込み型の演算子のオーバーロード、標準変換、キャスト/強制/暗黙のコンストラクタ-これらはすべて、ポリモーフィズムの微妙なサポートに貢献します。この回答の上部にある定義から、彼らはマッピングによって「タイプに適したコードを見つけて実行する」ことに取り組んでいます。
パラメータタイプから「離れて」
定数型の値からの「to」パラメトリック型
それらはそれ自体でポリモーフィックコンテキストを確立しませんが、そのようなコンテキスト内のコードを強化/簡素化するのに役立ちます。
あなたはだまされたと感じるかもしれません...それはそれほど多くないようです。重要なのは、パラメトリック多相コンテキスト(つまり、テンプレートまたはマクロ内)では、任意の広い範囲の型をサポートしようとしていますが、他の関数、リテラル、およびタイプの小さなセット。操作/値が論理的に同じである場合、タイプごとにほぼ同一の関数またはデータを作成する必要性を減らします。これらの機能は連携して「ベストエフォート」の姿勢を追加し、限られた利用可能な機能とデータを使用して直感的に期待されることを実行し、実際のあいまいさが存在する場合にのみエラーで停止します。
これは、ポリモーフィズムコードをサポートするポリモーフィズムコードの必要性を制限し、ポリモーフィズムの使用の周りに緊密なネットを描画して、ローカライズされた使用が広範囲の使用を強制しないようにし、実装を公開する必要のないコストを課すことなく、必要に応じてポリモーフィズムの利点を利用できるようにします。コンパイル時、使用される型をサポートするためにオブジェクトコードに同じ論理関数の複数のコピーを持ち、インライン化または少なくともコンパイル時に解決された呼び出しとは対照的に、仮想ディスパッチを実行します。C ++で一般的であるように、プログラマーには、ポリモーフィズムが使用される境界を制御するための多くの自由が与えられます。