仮想関数は、基本クラスに実装されたものではなく、派生クラスの特定の動作(読み取りメソッド)をオーバーライドし、実行時に基本クラスへのポインターを通じてオーバーライドする場合に使用します。
古典的な例は、Shape
という基本クラスと、それから派生する具体的な形状(クラス)がある場合です。各具象クラスは、Draw()
と呼ばれるオーバーライド(仮想メソッドを実装)します。
クラス階層は次のとおりです。
次のスニペットは、例の使用法を示しています。 Shape
クラスポインターの配列を作成します。各ポインターは個別の派生クラスオブジェクトを指します。実行時にDraw()
メソッドを呼び出すと、その派生クラスによってオーバーライドされたメソッドが呼び出され、特定のShape
が描画(またはレンダリング)されます。
Shape *basep[] = { &line_obj, &tri_obj,
&rect_obj, &cir_obj};
for (i = 0; i < NO_PICTURES; i++)
basep[i] -> Draw ();
上記のプログラムは、基本クラスへのポインタを使用して、派生クラスオブジェクトのアドレスを格納するだけです。 shape
の新しい具象派生クラスがいつでも追加される場合、プログラムは大幅に変更する必要がないため、これにより疎結合が提供されます。その理由は、具体的なShape
型に実際に使用(依存)する最小限のコードセグメントがあるためです。
上記は、有名な [〜#〜] solid [〜#〜] 設計原則の Open Closed Principle の良い例です。
同じ方法で異なるオブジェクトを処理する必要がある場合は、仮想関数を使用します。ポリモーフィズムと呼ばれます。古典的なShapeのようなベースクラスがあると想像してみましょう。
class Shape
{
public:
virtual void draw() = 0;
virtual ~Shape() {}
};
class Rectange: public Shape
{
public:
void draw() { // draw rectangle here }
};
class Circle: public Shape
{
public:
void draw() { // draw circle here }
};
これで、さまざまな形状のベクトルを持つことができます。
vector<Shape*> shapes;
shapes.Push_back(new Rectangle());
shapes.Push_back(new Circle());
そして、あなたはこのようなすべての形を描くことができます:
for(vector<Shape*>::iterator i = shapes.begin(); i != shapes.end(); i++)
{
(*i)->draw();
}
このようにして、1つの仮想メソッドdraw()で異なる形状を描画します。メソッドの適切なバージョンは、ポインターの背後にあるオブジェクトのタイプに関するランタイム情報に基づいて選択されます。
注意仮想関数を使用する場合、純粋な仮想(Shapeクラスのように、メソッドprotoの後に「= 0」を置くだけ)として宣言できます。この場合、純粋な仮想関数を使用してオブジェクトのインスタンスを作成することはできず、Abstractクラスと呼ばれます。
また、デストラクタの前に「仮想」に注意してください。基本クラスへのポインターを介してオブジェクトを操作する場合、デストラクタ仮想を宣言する必要があります。そのため、基本クラスポインタに対して「delete」を呼び出すと、デストラクタのチェーンがすべて呼び出され、メモリリークは発生しません。
動物のクラスを考えると、それに由来するのは猫、犬、牛です。動物のクラスには
virtual void SaySomething()
{
cout << "Something";
}
関数。
Animal *a;
a = new Dog();
a->SaySomething();
「Something」を印刷する代わりに、犬は「Bark」、猫は「Meow」と言う必要があります。この例では、aがDogであることがわかりますが、動物のポインターがあり、どの動物であるかわからない場合があります。あなたはそれがどの動物であるかを知りたくありません、あなたは動物に何かを言いたいだけです。したがって、仮想関数を呼び出すだけで、猫は「ニャー」と言い、犬は「樹皮」と言います。
もちろん、可能性のあるエラーを回避するために、SaySomething関数は純粋に仮想である必要がありました。
仮想オブジェクトを使用して、特にオブジェクトがある場合に「ポリモーフィズム」を実装します。実際の基礎となる型が何であるかはわかりませんが、それに対して実行する操作とその実装(方法実際に持っているタイプによって異なります)。
基本的に一般的に「リスコフ代替原理」と呼ばれるものは、1983年頃にこのことについて話したバーバラリスコフにちなんで名付けられました。
動的ランタイム決定を使用する必要がある場合、関数を呼び出すコードが呼び出された時点で、現在または将来、どのタイプがそれを通過するかがわからないため、これは使用するのに適したモデルです。
しかし、それは唯一の方法ではありません。データの「ブロブ」を取ることができるすべての種類の「コールバック」があり、入ってくるデータのヘッダーブロックに依存するコールバックのテーブルがあるかもしれません。メッセージプロセッサ。このために仮想関数を使用する必要はありません。実際、おそらく使用するのは、1つのエントリ(たとえば、1つの仮想関数のみを持つクラス)でのみvテーブルを実装する方法です。