「仮想関数」の版間の差分
(同じ利用者による、間の2版が非表示) | |||
行1: | 行1: | ||
− | + | [[仮想関数]]は、派生クラス(サブクラス)で再定義されるメンバ関数です。再定義されなくても構いません。基底クラス(スーパークラス)へのポインタや参照を使用して、派生クラスのオブジェクトを参照する場合でも、そのオブジェクトの[[仮想関数]]を呼び出し、派生クラスのバージョンの関数が実行できます。基底クラスの関数がメンバ関数を呼び出す場合に呼び出す関数が[[仮想関数]]であれば、サブクラスで上書きされている場合、派生クラスのメンバが呼び出されます。 | |
− | + | ||
− | + | ||
− | 読み方 | + | '''読み方''' |
;[[仮想関数]]: かそうかんすう | ;[[仮想関数]]: かそうかんすう | ||
行9: | 行7: | ||
== 概要 == | == 概要 == | ||
− | |||
[[仮想関数]] を用いない場合、ポインタの型のメンバが呼び出されます。 | [[仮想関数]] を用いない場合、ポインタの型のメンバが呼び出されます。 | ||
[[仮想関数]] を使用したときは、派生クラスのポインタを基底クラスのポインタで受け取っても、派生クラスのメンバ関数を呼び出します。 | [[仮想関数]] を使用したときは、派生クラスのポインタを基底クラスのポインタで受け取っても、派生クラスのメンバ関数を呼び出します。 | ||
− | |||
[[クラス]]を継承する場合には、デストラクタを[[仮想関数]]として定義する必要があります。[[継承と仮想デストラクタ]]をご参照ください。 | [[クラス]]を継承する場合には、デストラクタを[[仮想関数]]として定義する必要があります。[[継承と仮想デストラクタ]]をご参照ください。 | ||
− | |||
== 継承の例 == | == 継承の例 == | ||
− | |||
=== ソースコード extends_0.cpp === | === ソースコード extends_0.cpp === | ||
− | |||
<syntaxhighlight lang="cpp"> | <syntaxhighlight lang="cpp"> | ||
#include <iostream> | #include <iostream> | ||
行41: | 行34: | ||
}; | }; | ||
− | int main(int argc, char const* argv[]) | + | int |
+ | main(int argc, char const* argv[]) | ||
{ | { | ||
C1 c1; | C1 c1; | ||
行51: | 行45: | ||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | |||
=== コンパイル === | === コンパイル === | ||
− | |||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
− | + | c++ extends_0.cpp -o extends_0 | |
</syntaxhighlight> | </syntaxhighlight> | ||
− | |||
=== 実行例 === | === 実行例 === | ||
− | |||
[[クラス]] Bのx()は、f()を呼び出しています。 | [[クラス]] Bのx()は、f()を呼び出しています。 | ||
Bを[[継承]]したC1は、f()を[[オーバーライド]]しています。 | Bを[[継承]]したC1は、f()を[[オーバーライド]]しています。 | ||
行73: | 行63: | ||
void C1::f() | void C1::f() | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | |||
− | |||
== 仮想関数の例 == | == 仮想関数の例 == | ||
− | |||
親クラスの関数が呼び出している関数を[[オーバーライド]]しても、親クラスの関数を呼び出すと[[オーバーライド]]した関数を呼び出せませんでした。 | 親クラスの関数が呼び出している関数を[[オーバーライド]]しても、親クラスの関数を呼び出すと[[オーバーライド]]した関数を呼び出せませんでした。 | ||
一部の関数の機能変更をしたいときに、これでは、たくさん書き換えないといけなくなってしまいます。 | 一部の関数の機能変更をしたいときに、これでは、たくさん書き換えないといけなくなってしまいます。 | ||
そこで、[[継承]]して[[オーバーライド]]して拡張しやすくするには、基底クラスのメンバ関数を[[仮想関数]]にします。 | そこで、[[継承]]して[[オーバーライド]]して拡張しやすくするには、基底クラスのメンバ関数を[[仮想関数]]にします。 | ||
− | |||
=== ソースコード virtual_2.cpp === | === ソースコード virtual_2.cpp === | ||
− | |||
<syntaxhighlight lang="cpp"> | <syntaxhighlight lang="cpp"> | ||
#include <iostream> | #include <iostream> | ||
行113: | 行98: | ||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | |||
=== コンパイル === | === コンパイル === | ||
− | |||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
− | + | c++ virtual_2.cpp -o virtual_2 | |
</syntaxhighlight> | </syntaxhighlight> | ||
− | |||
=== 実行例 === | === 実行例 === | ||
− | |||
B の[[仮想関数]] f()は、C1 で[[オーバーライド]]しています。 | B の[[仮想関数]] f()は、C1 で[[オーバーライド]]しています。 | ||
C1 は、基底クラスの x() を呼び出すと、 x()は、C1 で[[オーバーライド]]した関数を呼び出しました。 | C1 は、基底クラスの x() を呼び出すと、 x()は、C1 で[[オーバーライド]]した関数を呼び出しました。 | ||
行129: | 行110: | ||
virtual void C1::f() | virtual void C1::f() | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | |||
− | |||
− | |||
− | |||
== 通常の継承の例 == | == 通常の継承の例 == | ||
− | |||
この例では、[[仮想関数]]を使用していません。 | この例では、[[仮想関数]]を使用していません。 | ||
− | |||
=== ソースコード extends_1.cpp === | === ソースコード extends_1.cpp === | ||
− | |||
<syntaxhighlight lang="cpp"> | <syntaxhighlight lang="cpp"> | ||
#include <iostream> | #include <iostream> | ||
行171: | 行145: | ||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | |||
=== コンパイル === | === コンパイル === | ||
− | |||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
− | + | c++ extends_1.cpp -o extends_1 | |
</syntaxhighlight> | </syntaxhighlight> | ||
− | |||
=== 実行例 === | === 実行例 === | ||
− | |||
[[クラス]] B と C1 は、両方とも基底クラスのf()を呼び出しています。 | [[クラス]] B と C1 は、両方とも基底クラスのf()を呼び出しています。 | ||
− | |||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
% ./extends_1 | % ./extends_1 | ||
行187: | 行156: | ||
void B::f() | void B::f() | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | |||
== 仮想関数の例 == | == 仮想関数の例 == | ||
− | |||
=== ソースコード virtual_1.cpp === | === ソースコード virtual_1.cpp === | ||
− | |||
<syntaxhighlight lang="cpp"> | <syntaxhighlight lang="cpp"> | ||
#include <iostream> | #include <iostream> | ||
行224: | 行190: | ||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | |||
=== コンパイル === | === コンパイル === | ||
− | |||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
− | + | c++ virtual_1.cpp -o virtual_1 | |
</syntaxhighlight> | </syntaxhighlight> | ||
− | |||
=== 実行例 === | === 実行例 === | ||
− | |||
クラス B のポインタにC1のアドレスを渡して、f() を呼び出すと、C1のメンバ関数が呼び出されています。 | クラス B のポインタにC1のアドレスを渡して、f() を呼び出すと、C1のメンバ関数が呼び出されています。 | ||
− | |||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
% ./virtual_1 | % ./virtual_1 | ||
行240: | 行201: | ||
virtual void C1::f() | virtual void C1::f() | ||
</syntaxhighlight> | </syntaxhighlight> | ||
+ | == 継承と仮想関数とオーバーライドの関係 == | ||
+ | スーパークラスで定義されたメンバが仮想関数である場合と、そうでない場合でどの関数が呼び出されるのかの例です。 | ||
+ | * クラス CV のメンバ関数 x が メンバの仮想関数 f を呼び出すなら, CV を継承してできた C1 で f をオーバーライドしても、x を呼び出したら スーパークラスの f が呼び出されます。 | ||
+ | * クラス C のメンバ関数 x が メンバ関数 f を呼び出すなら, C を継承してできた C2 で f をオーバーライドすると、x を呼び出したら サブクラスの f が呼び出されます。 | ||
+ | === ソースコード virtual_3.cpp === | ||
+ | <syntaxhighlight lang="cpp"> | ||
+ | #include <iostream> | ||
+ | using namespace std; | ||
− | + | class CV { | |
+ | public: | ||
+ | void x () { | ||
+ | f(); | ||
+ | } | ||
+ | // 仮想関数 | ||
+ | virtual void f() { | ||
+ | cout << __PRETTY_FUNCTION__ << endl; | ||
+ | } | ||
+ | }; | ||
+ | |||
+ | class C1 : public CV { | ||
+ | public: | ||
+ | void f() { | ||
+ | cout << __PRETTY_FUNCTION__ << endl; | ||
+ | } | ||
+ | }; | ||
+ | |||
+ | class C { | ||
+ | public: | ||
+ | void x () { | ||
+ | f(); | ||
+ | } | ||
+ | void f() { | ||
+ | cout << __PRETTY_FUNCTION__ << endl; | ||
+ | } | ||
+ | }; | ||
+ | |||
+ | class C2 : public C { | ||
+ | public: | ||
+ | void f() { | ||
+ | cout << __PRETTY_FUNCTION__ << endl; | ||
+ | } | ||
+ | }; | ||
+ | |||
+ | |||
+ | int | ||
+ | main(int argc, char const* argv[]) | ||
+ | { | ||
+ | C1 c1; | ||
+ | C2 c2; | ||
+ | |||
+ | c1.x(); | ||
+ | c2.x(); | ||
+ | |||
+ | return 0; | ||
+ | } | ||
+ | </syntaxhighlight> | ||
+ | === コンパイル === | ||
+ | <syntaxhighlight lang="bash"> | ||
+ | c++ virtual_3.cpp -o virtual_3 | ||
+ | </syntaxhighlight> | ||
+ | === 実行例 === | ||
+ | <syntaxhighlight lang="bash"> | ||
+ | % ./virtual_3 | ||
+ | virtual void C1::f() | ||
+ | void C::f() | ||
+ | </syntaxhighlight> | ||
+ | == 関連項目 == | ||
* [[純粋仮想関数]] | * [[純粋仮想関数]] | ||
* [[クラス]] | * [[クラス]] | ||
* [[継承と仮想デストラクタ]] | * [[継承と仮想デストラクタ]] | ||
+ | <!-- | ||
+ | vim: filetype=mediawiki | ||
+ | --> |
2016年1月14日 (木) 15:18時点における最新版
仮想関数は、派生クラス(サブクラス)で再定義されるメンバ関数です。再定義されなくても構いません。基底クラス(スーパークラス)へのポインタや参照を使用して、派生クラスのオブジェクトを参照する場合でも、そのオブジェクトの仮想関数を呼び出し、派生クラスのバージョンの関数が実行できます。基底クラスの関数がメンバ関数を呼び出す場合に呼び出す関数が仮想関数であれば、サブクラスで上書きされている場合、派生クラスのメンバが呼び出されます。
読み方
- 仮想関数
- かそうかんすう
目次
概要
仮想関数 を用いない場合、ポインタの型のメンバが呼び出されます。 仮想関数 を使用したときは、派生クラスのポインタを基底クラスのポインタで受け取っても、派生クラスのメンバ関数を呼び出します。
クラスを継承する場合には、デストラクタを仮想関数として定義する必要があります。継承と仮想デストラクタをご参照ください。
継承の例
ソースコード extends_0.cpp
#include <iostream> using namespace std; class B { public: void x() { f(); } void f() { cout << __PRETTY_FUNCTION__ << endl; } }; class C1 : public B { public: void f() { cout << __PRETTY_FUNCTION__ << endl; } }; int main(int argc, char const* argv[]) { C1 c1; c1.x(); c1.f(); return 0; }
コンパイル
c++ extends_0.cpp -o extends_0
実行例
クラス Bのx()は、f()を呼び出しています。 Bを継承したC1は、f()をオーバーライドしています。
C1のインスタンスc1のxを呼び出すと、基底クラスのf()を呼び出しています。 c1のf()を呼び出すとC1のf()が呼び出されます。
このように、メンバ関数をオーバーライドしても親クラスの関数に影響していません。
% ./extends_0 void B::f() void C1::f()
仮想関数の例
親クラスの関数が呼び出している関数をオーバーライドしても、親クラスの関数を呼び出すとオーバーライドした関数を呼び出せませんでした。 一部の関数の機能変更をしたいときに、これでは、たくさん書き換えないといけなくなってしまいます。
そこで、継承してオーバーライドして拡張しやすくするには、基底クラスのメンバ関数を仮想関数にします。
ソースコード virtual_2.cpp
#include <iostream> using namespace std; class B { public: void x() { f(); } virtual void f() { cout << __PRETTY_FUNCTION__ << endl; } }; class C1 : public B { public: void f() { cout << __PRETTY_FUNCTION__ << endl; } }; int main(int argc, char const* argv[]) { C1 c1; c1.x(); return 0; }
コンパイル
c++ virtual_2.cpp -o virtual_2
実行例
B の仮想関数 f()は、C1 でオーバーライドしています。 C1 は、基底クラスの x() を呼び出すと、 x()は、C1 でオーバーライドした関数を呼び出しました。
% ./virtual_2 virtual void C1::f()
通常の継承の例
この例では、仮想関数を使用していません。
ソースコード extends_1.cpp
#include <iostream> using namespace std; class B { public: void f() { cout << __PRETTY_FUNCTION__ << endl; } }; class C1 : public B { public: void f() { cout << __PRETTY_FUNCTION__ << endl; } }; int main(int argc, char const* argv[]) { B b; C1 c1; B *p; p = &b; p->f(); p = &c1; p->f(); return 0; }
コンパイル
c++ extends_1.cpp -o extends_1
実行例
クラス B と C1 は、両方とも基底クラスのf()を呼び出しています。
% ./extends_1 void B::f() void B::f()
仮想関数の例
ソースコード virtual_1.cpp
#include <iostream> using namespace std; class B { public: virtual void f() { cout << __PRETTY_FUNCTION__ << endl; } }; class C1 : public B { public: void f() { cout << __PRETTY_FUNCTION__ << endl; } }; int main(int argc, char const* argv[]) { B b; C1 c1; B *p; p = &b; p->f(); p = &c1; p->f(); return 0; }
コンパイル
c++ virtual_1.cpp -o virtual_1
実行例
クラス B のポインタにC1のアドレスを渡して、f() を呼び出すと、C1のメンバ関数が呼び出されています。
% ./virtual_1 virtual void B::f() virtual void C1::f()
継承と仮想関数とオーバーライドの関係
スーパークラスで定義されたメンバが仮想関数である場合と、そうでない場合でどの関数が呼び出されるのかの例です。
- クラス CV のメンバ関数 x が メンバの仮想関数 f を呼び出すなら, CV を継承してできた C1 で f をオーバーライドしても、x を呼び出したら スーパークラスの f が呼び出されます。
- クラス C のメンバ関数 x が メンバ関数 f を呼び出すなら, C を継承してできた C2 で f をオーバーライドすると、x を呼び出したら サブクラスの f が呼び出されます。
ソースコード virtual_3.cpp
#include <iostream> using namespace std; class CV { public: void x () { f(); } // 仮想関数 virtual void f() { cout << __PRETTY_FUNCTION__ << endl; } }; class C1 : public CV { public: void f() { cout << __PRETTY_FUNCTION__ << endl; } }; class C { public: void x () { f(); } void f() { cout << __PRETTY_FUNCTION__ << endl; } }; class C2 : public C { public: void f() { cout << __PRETTY_FUNCTION__ << endl; } }; int main(int argc, char const* argv[]) { C1 c1; C2 c2; c1.x(); c2.x(); return 0; }
コンパイル
c++ virtual_3.cpp -o virtual_3
実行例
% ./virtual_3 virtual void C1::f() void C::f()