2012-01-11 22 views
30

可能性の重複:
Can someone explain C++ Virtual Methods?なぜ仮想関数を使用するのですか?

私はC++の仮想関数にについて質問があります。

なぜ仮想関数を使用するのですか?誰も私にリアルタイム実装や仮想関数の使用を教えてもらえますか?

+1

子供が「なぜ仮想機能を使うのですか」と尋ねたときに説明するのが難しいと思ったので、私はこのポストのためだけにブログ投稿を作成しましたか? http://nrecursions.blogspot.in/2015/06/so-why-do-we-need-virtual-functions.html – Nav

答えて

45

基本クラスに実装されているものではなく、派生クラスの特定の動作(読み込みメソッド)をオーバーライドし、実行時に基本クラスへのポインタを使用して実行する場合に、仮想関数を使用します。

古典的な例は、Shapeという基底クラスとそれから派生した具象シェイプ(クラス)がある場合です。各具象クラスは、Draw()という名前でオーバーライドされます(仮想メソッドを実装します)。

次のようにクラス階層は、次のとおり

Class hierarchy

次のスニペットは、例の使用を示す図です。 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の良い例です。

+7

'virtual'を使用せず、' Line'のようなサブクラスで再定義したらどうなりますか?と三角形。何が違いますか? – bluejamesbond

+4

関数が基本クラスで仮想でない場合は、基本クラスポインタを介して派生クラスのバージョンにアクセスできませんでした。 – radensb

+0

申し訳ありませんが、まだ少し混乱しています。仮想関数は、実行時までに必要な関数のバージョンが分からない場合に適していますが、その前にしばらくしてベースクラスのインスタンスを作成する必要があります。なぜなら、仮想関数がなくても、実行時までサブクラスを作成して、適切なクラスをインスタンス化するだけで、何が必要なのか分からないように思えるからです。だから、あなたが*重要である前に、ベースクラスのインスタンス化が必要になるかもしれませんね。別名:基本ポインタ – krb686

1

仮想関数を使用して、 "ポリモーフィズム"を実装します。特に、オブジェクトを持っていて、実際の基底型がわからないのですが、実行する操作がわかっていて、これ(どのようにそれを行う)はあなたが実際に持っているタイプによって異なります。

基本的に一般的にあなたは、ポイントでの関数を呼び出すコードが呼び出された動的な実行時の決定を使用する必要が1983年

の周りにこのことについて話をしたバーバラ・リスコフにちなんで名付けられた「リスコフの置換原則」と呼ばれているものあなたは、現在または将来、どのタイプがそれを通過するかを知らないので、これは良いモデルです。

しかしそれだけではありません。データの「小塊(blob)」を取ることができるあらゆる種類の「コールバック」があり、データのヘッダブロックに依存するコールバックのテーブルを持つことがあります。メッセージプロセッサ。このため、仮想関数を使用する必要はありません。おそらく実際には、1つのエントリ(たとえば、仮想関数が1つしかないクラス)でvテーブルを実装する方法があります。

20

動物のクラスを考えると、それは猫、犬と牛です。動物クラスは、

virtual void SaySomething() 
{ 
    cout << "Something"; 
} 

機能を持っています。

Animal *a; 
a = new Dog(); 
a->SaySomething(); 

の代わりに「何か」を印刷し、犬は「樹皮」と言うべき、猫は「ニャー」と言うべきです。この例では、aが犬であることがわかりますが、動物のポインタがあり、それがどの動物かわからないことがあります。あなたはそれがどの動物であるかを知りたくはありません。動物に何か言いたいだけです。だからあなたは仮想関数を呼び出すだけで、猫は「ヤング」と言い、犬は「樹皮」と言うでしょう。

もちろん、可能性のあるエラーを避けるために、SaySomething関数は純粋仮想であったはずです。

+2

で派生した関数を呼び出す満足感がありました。 – haris

+0

"SaySomething関数は可能なエラーを避けるためには純粋な仮想関数であったはずです。"これの例を挙げてください。 – user454083

+3

遅く返事を申し訳ありません。 これを考慮すると、開発者は動物クラス(例えば 'Fox')を継承する新しいクラスを作成し、SaySomethingメソッドをオーバーライドするのを忘れてしまいます。私が提供した仮想メソッドを使用すると、 'Fox'インスタンスは「Something」と言うでしょう。これは正しくありません。 SaySomethingを純粋な仮想として宣言すれば、 'Fox'をインスタンス化することはできません。' newFox(...)を含むコードはエラーになります。このようにして、 'Fox'クラスを作成した開発者は、コンパイル時の間違いについて通知されます。時間を浪費しないので、コンパイル時エラーは良いです:) – holgac

20

同じ方法で異なるオブジェクトを処理する必要がある場合は、仮想関数を使用します。それは多型と呼ばれています。あなたは、いくつかの基本クラスを持っている想像してみましょう - 古典形のようなもの:

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()を使用してさまざまな図形を描画しています。メソッドの適切なバージョンは、ポインタの後ろにあるオブジェクトのタイプに関する実行時の情報に基づいて選択されます。

お知らせ あなたは仮想関数を使用するときは、(クラスの形のように、単にメソッドのプロト後に「= 0」を配置)純粋仮想として宣言することができます。この場合、純粋仮想関数を持つオブジェクトのインスタンスを作成することはできません。これはAbstractクラスと呼ばれます。

また、デストラクタの前に "virtual"に注意してください。ベースクラスのポインタを使ってオブジェクトを使って作業をする場合は、デストラクタバーチャルを宣言する必要があります。したがって、ベースクラスポインタの "delete"を呼び出すと、すべてのデストラクタチェーンが呼び出され、メモリリークは発生しません。

関連する問題