class base{
.....
virtual void function1();
virtual void function2();
};
class derived::public base{
int function1();
int function2();
};
int main()
{
derived d;
base *b = &d;
int k = b->function1() // Why use this instead of the following line?
int k = d.function1(); // With this, the need for virtual functions is gone, right?
}
私はCompSciエンジニアではないので、これを知りたいです。基本クラスのポインタを回避できるのに、なぜ仮想関数を使用するのですか?
ポリモーフィズムの力は、単純な例では実際には明らかではありませんが、少し拡張すると、より明確になる可能性があります。
class vehicle{
.....
virtual int getEmission();
}
class car : public vehicle{
int getEmission();
}
class bus : public vehicle{
int getEmission();
}
int main()
{
car a;
car b;
car c;
bus d;
bus e;
vehicle *traffic[]={&a,&b,&c,&d,&e};
int totalEmission=0;
for(int i=0;i<5;i++)
{
totalEmission+=traffic[i]->getEmission();
}
}
これにより、ポインタのリストを反復処理し、基になる型に応じてさまざまなメソッドを呼び出すことができます。基本的に、コンパイル時に子タイプが何であるかを知る必要がないコードを記述できますが、コードはとにかく正しい機能を実行します。
正解です。オブジェクトがある場合は、ポインタを介してそれを参照する必要はありません。また、オブジェクトが作成されたタイプとして破棄される場合は、仮想デストラクタは必要ありません。
このユーティリティは、別のコードからオブジェクトへのポインタを取得し、最も派生した型が何であるかを実際に知らないときに提供されます。同じベース上に2つ以上の派生型を構築し、ベース型へのポインターを返す関数を使用できます。仮想関数を使用すると、オブジェクトを破棄するときまで、使用している派生型を気にせずにポインターを使用できます。仮想デストラクタは、オブジェクトがどの派生クラスに対応するかを知らなくても、オブジェクトを破棄します。
仮想関数を使用する最も簡単な例を次に示します。
base *b = new derived;
b->function1();
delete b;
ポリモーフィズムを実装すること。派生オブジェクトを指す基本クラスポインタがない限り、ここでポリモーフィズムを持つことはできません。
派生クラスの重要な機能の1つは、派生クラスへのポインターがその基本クラスへのポインターと型互換であることです。ポリモーフィズムは、このシンプルでありながら強力で用途の広い機能を利用する技術であり、オブジェクト指向の方法論を最大限に活用します。
C++では、基本クラスのポインターまたは参照がプログラマーの介入なしに派生クラスのサブタイプのいずれかをアドレス指定できる、特別なタイプ/サブタイプの関係が存在します。ポインタまたは基本クラスへの参照を使用して複数の型を操作するこの機能は、ポリモーフィズムと呼ばれます。
サブタイプポリモーフィズムを使用すると、操作する個々のタイプに関係なく、アプリケーションのカーネルを記述できます。むしろ、基本クラスのポインターと参照を介して、抽象化の基本クラスのパブリックインターフェイスをプログラムします。実行時に、参照されている実際の型が解決され、パブリックインターフェイスの適切なインスタンスが呼び出されます。呼び出す適切な関数の実行時解決は、動的バインディングと呼ばれます(デフォルトでは、関数はコンパイル時に静的に解決されます)。 C++では、動的バインディングは、クラス仮想関数と呼ばれるメカニズムを通じてサポートされます。継承と動的バインディングによるサブタイプのポリモーフィズムは、オブジェクト指向プログラミングの基盤を提供します
継承階層の主な利点は、継承階層を形成する個々の型ではなく、抽象基本クラスのパブリックインターフェイスにプログラムできることです。これにより、コードがその階層の変更から保護されます。たとえば、eval()を抽象Query基本クラスのパブリック仮想関数として定義します。
_rop->eval();
などのコードを記述することにより、ユーザーコードはクエリ言語の多様性と変動性から保護されます。これにより、ユーザープログラムを変更せずに型を追加、改訂、または削除できるだけでなく、新しいクエリ型のプロバイダーは、階層自体のすべての型に共通する動作やアクションを再コーディングする必要がなくなります。これは、継承の2つの特別な特性であるポリモーフィズムと動的バインディングによってサポートされます。 C++内のポリモーフィズムについて話すとき、私たちは主に、派生クラスのいずれかに対処するための基本クラスのポインターまたは参照の機能を意味します。たとえば、非メンバー関数eval()を次のように定義すると、// pqueryはQueryvoid eval( const Query *pquery ) { pquery->eval(); }
から派生した任意のクラスをアドレス指定でき、合法的に呼び出すことができ、任意のオブジェクトのアドレスを渡します。 4つのクエリタイプ:
int main()
{
AndQuery aq;
NotQuery notq;
OrQuery *oq = new OrQuery;
NameQuery nq( "Botticelli" ); // ok: each is derived from Query
// compiler converts to base class automatically
eval( &aq );
eval( ¬q );
eval( oq );
eval( &nq );
}
一方、クエリから派生していないオブジェクトのアドレスを使用してeval()を呼び出そうとすると、コンパイル時エラーが発生します。
int main()
{ string name("Scooby-Doo" ); // error: string is not derived from Query
eval( &name);
}
Eval()内で、pquery-> eval();を実行します。実際のクラスオブジェクトのpqueryアドレスに基づいて、適切なeval()仮想メンバー関数を呼び出す必要があります。前の例では、pqueryは、AndQueryオブジェクト、NotQueryオブジェクト、OrQueryオブジェクト、およびNameQueryオブジェクトを順番にアドレス指定します。プログラムの実行中の各呼び出しポイントで、pqueryによってアドレス指定された実際のクラスタイプが決定され、適切なeval()インスタンスが呼び出されます。動的バインディングは、これを実現するためのメカニズムです。オブジェクト指向パラダイムでは、プログラマーは、バインドされているが無限のタイプのセットの未知のインスタンスを操作します。 (型のセットはその継承階層によってバインドされます。ただし、理論的には、その階層の深さと幅に制限はありません。)C++では、これは基本クラスのポインターと参照のみを介してオブジェクトを操作することによって実現されます。オブジェクトベースのパラダイムでは、プログラマーは、コンパイルの時点で完全に定義されている固定された特異型のインスタンスを操作します。オブジェクトのポリモーフィック操作では、ポインターまたは参照のいずれかを介してオブジェクトにアクセスする必要がありますが、C++でのポインターまたは参照の操作自体は、必ずしもポリモーフィズムをもたらすとは限りません。たとえば、
// no polymorphism
int *pi;
// no language-supported polymorphism
void *pvi;
// ok: pquery may address any Query derivation
Query *pquery;
C++では、ポリモーフィズムは個々のクラス階層内にのみ存在します。 void *型のポインターは多態性として記述できますが、明示的な言語サポートがありません。つまり、明示的なキャストと、アドレス指定されている実際の型を追跡する何らかの形式の判別式を使用して、プログラマーが管理する必要があります。
あなたは2つの質問をしたようです(タイトルと最後に):
派生クラスに基本クラスポインタを使用するのはなぜですか?これはまさにポリモーフィズムの使用です。これにより、特定の実装を可能にしながら、オブジェクトを均一に処理できます。これが気になる場合は、次のように尋ねる必要があると思います。なぜポリモーフィズムなのか。
基本クラスのポインタを回避できるのに、なぜ仮想デストラクタを使用するのですか?ここでの問題は基本クラスのポインタを常に回避できるとは限りませんポリモーフィズムの強さを利用することです。