ポリモーフィズム機能を持っていることを説明するポインターのない C++ ポリモーフィズムを以下に示し ます。C++ はポインターまたは参照型を使用する必要があります。
私はさらにいくつかのリソースを調べましたが、それらはすべて同じことを言っていますが、理由は.
値でポリモーフィズムをサポートする技術的な問題はありますか?それとも可能ですが、C++ はその機能を提供しないことに決めましたか?
ポリモーフィズム機能を持っていることを説明するポインターのない C++ ポリモーフィズムを以下に示し ます。C++ はポインターまたは参照型を使用する必要があります。
私はさらにいくつかのリソースを調べましたが、それらはすべて同じことを言っていますが、理由は.
値でポリモーフィズムをサポートする技術的な問題はありますか?それとも可能ですが、C++ はその機能を提供しないことに決めましたか?
値をポリモーフィックに処理する際の問題は、オブジェクトのスライシングの問題に要約されます。派生オブジェクトは基底クラスよりも多くのメモリを使用する可能性があるため、自動ストレージ (つまり、スタック上) で値を宣言すると、基底クラスではなく基底クラスにのみメモリが割り当てられることになります。派生オブジェクト。したがって、派生クラスに属するオブジェクトの一部が切り取られることがあります。そのため、C++ の設計者は、派生クラスのデータ メンバーに触れることができない、仮想メンバー関数を基本クラスの実装に再ルーティングするという意識的な決定を下しました。
問題は、オブジェクトと呼ばれるものが自動メモリ (スタック上) に割り当てられ、サイズがコンパイル時にわかっている必要があるという事実から生じます。
ポインターのサイズは、ポインターが何を指しているかに関係なくコンパイル時に認識され、参照は内部でポインターとして実装されるため、心配する必要はありません。
ただし、オブジェクトを検討してください。
BaseObject obj = ObjectFactory::createDerived();
条件付きで派生オブジェクトを返すobj
場合、どのくらいのメモリを割り当てる必要がありますか? createDerived()
これを克服するために、返されたオブジェクトはスライスBaseObject
され、サイズがわかっているに「変換*されます。
これはすべて、「使用した分だけ支払う」という考え方から生じています。
「値を持つポリモーフィズム」が何を意味するのかはすぐにはわかりません。C++ では、 type のオブジェクトがある場合A
、常に type のオブジェクトとして動作しますA
。これは完全に正常であり、当然のことです。他の方法でどのように動作できるかわかりません。ですから、誰かが「提供しない」と決めた「能力」のことを言っているのか明確ではありません。
C++ におけるポリモーフィズムとは、1 つのことを意味します。ポリモーフィック型の式を介して行われた仮想関数呼び出しは、その式の動的型に従って解決されます (非仮想関数の静的型とは対照的です)。それだけです。
C++ のポリモーフィズムは、常に上記の規則に従って機能します。ポインターを介してそのように機能します。参照を通じてそのように機能します。直接のオブジェクト(あなたが呼んだ「値」)を介してそのように機能します。したがって、C++ のポリモーフィズムがポインターと参照でのみ機能すると言うのは正しくありません。「値」でも機能します。上記のように、それらはすべて同じ規則に従います。
ただし、即時オブジェクト (「値」) の場合、その動的型は常に静的型と同じです。したがって、ポリモーフィズムは即値に対して機能しますが、真に「ポリモーフィック」であることを示しているわけではありません。ポリモーフィズムのある即時オブジェクトの動作は、ポリモーフィズムがない場合と同じです。したがって、直接オブジェクトのポリモーフィズムは退化した自明なポリモーフィズムです。概念的にのみ存在します。これもまた完全に論理的です: typeA
のオブジェクトは type のオブジェクトとして振る舞うべきA
です。他にどのように振る舞うことができますか?
実際の非縮退ポリモーフィズムを観察するには、静的型が動的型と異なる式が必要です。非自明なポリモーフィズムは、静的型の式A
が (仮想関数呼び出しに関して) 異なる型のオブジェクトとして動作する場合に観察されますB
。このため、 static 型の式A
は実際には type のオブジェクトを参照する必要がありますB
。これは、ポインターまたは参照でのみ可能です。式の静的型と動的型の違いを作成する唯一の方法は、ポインターまたは参照を使用することです。
つまり、C++ での多態性はポインターまたは参照を介してのみ機能すると言うのは正しくありません。ポインターまたは参照を使用すると、ポリモーフィズムが観察可能になり、自明ではなくなります。
簡単な答えは、標準で指定されているためです。しかし、それを許可する上で乗り越えられない技術的障壁はありますか?
C++ データ構造には既知のサイズがあります。通常、ポリモーフィズムでは、データ構造のサイズを変更できる必要があります。一般に、小さい型のストレージ内に別の (大きい) 型を格納することはできないため、親クラスのストレージ内に余分な変数 (または他の理由で大きくなる) を含む子クラスを格納することは、通常は不可能です。
これで、これを回避できます。親クラスを格納するのに必要なサイズよりも大きなバッファーを作成し、そのバッファー内に子クラスを作成することもできます。ただし、この場合、前述のインスタンスへの公開は参照を介して行われ、慎重にクラスをラップします。
boost::any
これは、boost::variant
や の多くの実装で使用される「スモール オブジェクト最適化」と呼ばれる手法に似ています。この手法std::string
では、クラス内のバッファにオブジェクトを (値によって) 格納し、それらの有効期間を手動で管理します。
Derived
インスタンスへのポインターがインスタンスへのポインターとは異なる値を持つ可能性があるという問題もありBase
ます。C++ のオブジェクトの値インスタンスは、ほとんどの実装でインスタンスのストレージが開始する場所に存在すると推定されます。
Derived
したがって、理論的には、 C++ は、 と の両方に同じ「ポインター」値を使用して、同じメモリ フットプリントに格納できる派生クラスに制限した場合、ポリモーフィック インスタンスを許可できますBase
が、これは非常に狭いコーナー ケースであり、可能性があります。ほぼすべてのケースで、コンパイラがクラスの値インスタンスに関して行う可能性のある最適化と仮定の種類を減らします! (現在、コンパイラは、例として、クラスの値インスタンスが他の場所でオーバーライドされていないメソッドをC
持っていると想定できますvirtual
) これは、非常にわずかな利益のための重要なコストです。
さらに、C++ 言語を使用して、既存の言語機能 (新しい配置、参照、および手動破棄) を使用して、上記のコストを課すことなく、このコーナー ケースをエミュレートすることができます。