プログラムの質問ではないですが、教えてください。
最近、Visual C++ をはじめたのですが、どうも仮想関数というものを使う必要性がわか
りません。
どんなときに使うのか?やこのような場合は仮想関数でなければいけないなどがあれば
教えてください。
すみませんがお願いします。
大抵のC++参考書に説明があります。お読みになりましたか?
参考書を読んでどんなものかはわかりましたが、いまいち使用目的がわからないんです
よね。
それで困っていたんです。
> いまいち使用目的がわからないんですよね。
派生したクラスごとに動作を変えたいときです。
基底クラスのポインタなどからメンバ関数へアクセスするときに役立ちます。
まあ、使わなくていいのなら無理して使うこともないですよ。
・・・といいたい所だけど、仮想関数ってすごく重要なのでそうもいってられないかな。
オブジェクト指向の入門書でも読めば「ああ、なるほど!」って思えるはず。
あとはEffectiveC++かなぁ。
# それだけで納得できる人なら最初から質問しないだろうとか思ったり...
いつも使っている例(何回か書いた)
//「画面に描画できるものクラス」
//描画できる性質を持っているが、具体的にどんなふうに描画するかは関知しない
class drawable { ...
virtual void draw()=0;
};
//直線クラス、長方形クラス、円クラス、その他
//「描画できる性質」は共通だが、具体的にどんな図を描くかはすべて違う
class line : public drawable { ...
virtual void draw() { ... }
};
class rectangle : public drawable { ... } // 詳細略
class circle : public drawable { ... } // 詳細略
// 「画面に描画できるもの」であるなら、何でも受け取れる関数
// 実際には line かもしれないし rectangle かもしれない
void drawit(drawable* obj) {
obj->draw();
}
drawit() は drawable クラスの draw() を字面上呼び出しています。
が、この draw() は virtual なので、この字面上の呼び出しは実際には
派生クラス (line/rectangle/circle) の draw() を呼び出します。
実行時までどのクラスの draw() を呼ぶべきか判らない、動的解決が必要な場合に
virtual を使います。
私が良く使うパターンだと
class CShape { ...
public:
virtual void draw() { ... }
};
class Polyline : public CShape { ... };
class Polygon : public CShape { ... };
class Circle : public CShape { ... };
class XXX { ...
CShape* m_shapes[10];
void DrawShapes();
};
void XXX::DrawShapes()
{
int i;
for(i = 0; i < 10; i++)
{
if(m_shapes[i] != NULL)
{
m_shapes[i]->draw();
}
}
}
相当省略していますのでそのまま使えませんが、
要はm_shapesにはCShapeクラスから派生したさまざまなクラスが入りえますが、
その全ての描画を上記のコードで呼び出すことが出来ます。
これが、仮想関数を使わないと、それぞれのインスタンスが何者かで
呼び出すためのコードを変えなくてはならなくなります。
これによってコードが非常にすっきりすることがわかると思います。
また、CShapeから派生し、drawメソッドをオーバーライドしていれば、
新しいクラスを追加しても呼出側は影響を受けません。
drawの実装を強制したいのであれば、
class CShape { ...
public:
virtual void draw() = 0;
};
として純粋仮想関数にすれば、派生側の実装を縛ることも可能です。
こういう使い方をするには設計そのものをオブジェクト指向でやってないと
無理があるかもしれません。
コールバック関数はご存知ですか?
Cの標準関数のbsearch()やqsort()に渡し、内部で使われる関数がそれです。
これの「メンバ関数版」が(純粋)仮想関数です。
細かい違いはあれど、動作自体はまさしくそれです。
> 細かい違いはあれど、動作自体はまさしくそれです。
ここではメカニズム(からくり)の話はしてないと思う。
>ここではメカニズム(からくり)の話はしてないと思う。
ん? どういう事でしょう?ちょっとよく分かりませんでした(^^;
「どんなときに使うのか?」という質問だったので、
「コールバック関数のように使う」というお返事をしたのでした。
>επιστημηさん
ああ、分かりました!
「私が言った「動作自体」というのが、仮想関数を動作させる為の内部的なからくり
を指している」という事を仰ってるのですね。
失礼乍、言葉足らずでした(^^;
「動作自体」はそれを指しているのでなく、「コールバック関数が内部で呼ばれる」
事を指しています。
> それだけで納得できる人なら最初から質問しないだろうとか思ったり...
ぎゃ。そうですよね、大変失礼しました。。。
#サンプルも書いたんだけど長くなったので消しちゃったんです^^;
これだけでは無責任な感じなので追加します。
コードはもう出ているのですが僕なりにも書いてみました。
class text { //テキスト
public:
char* str;
virtual void write() { cout << str; }; //普通に書く
};
class comment : public text { //コメント
public:
virtual void write() { cout << ( << str << ); }; //括弧でくくって書く
};
class note : public text { //注釈
public:
virtual void write() { cout << note: << str; }; //頭にnote:を付けて書く
};
これで、例えば次のような関数にtextやcomment,note型などのポインタが
混ざった配列を渡しても、きちんとcomment型だったものは括弧でくくられ、
note型だったものはnote:がついて出力できるんです。
write_text(text* p /*textの配列*/, int n /*配列の要素数*/) {
for(int i=0;i<n;++i)
p->write();
};
こんな感じで派生したクラスたちを
配列などで一括管理するときに仮想関数は使えると思います。
>「どんなときに使うのか?」という質問だったので、
>「コールバック関数のように使う」というお返事をしたのでした。
うーん…むしろ「コールバック関数のように使うことができる」ではないかしら。
図形に属するならば円だろが三角だろが四角だろが、
それぞれに応じた'描画'を用意しておけば、
なんか知らんけどとにかく図形に対して'描画'と言えばよきに計らってくれる。
それが(仮想関数で実現された)'多態'のキモであり使いみちではないかと。
>むしろ「コールバック関数のように使うことができる」ではないかしら。
あ、その方が正確かな?どうも有難う御座います。
「クラスインスタンスに対してのコールバック関数みたいな奴」って感じで。
Cで多態させるとvoid*やら何やらで危なっかしいんですが、C++ではきちんと自動生成し
てくれますよね。初めて実務で使った時は、随分感動しましたよ~。
正直、これほどのスレがあるとは思っておりませんでした。
ほんとに皆様、どうもありがとうございます。
参考書を見たときに、virtual をつければ仮想関数です・・・みたいな柿か確かしてい
なかったので。
ついでといっては何ですが、VC++ .NET またはオブジェクト指向を独学で学ぶのによい
参考書を知っていたら教えてください。
現在の私のレベルは入門者ととっていただいてかまいませんので、その辺も踏まえてお
願いいたします。
あつかましくてすみません。