「仮想関数」の版間の差分

提供: C++入門
移動: 案内検索
(ページの作成:「<!-- vim: filetype=mediawiki --> 読み方 ;仮想関数: かそうかんすう __TOC__ == 概要 == 仮想関数 を用いない場合、ポインタの型...」)
 
 
(同じ利用者による、間の4版が非表示)
行1: 行1:
<!--
+
[[仮想関数]]は、派生クラス(サブクラス)で再定義されるメンバ関数です。再定義されなくても構いません。基底クラス(スーパークラス)へのポインタや参照を使用して、派生クラスのオブジェクトを参照する場合でも、そのオブジェクトの[[仮想関数]]を呼び出し、派生クラスのバージョンの関数が実行できます。基底クラスの関数がメンバ関数を呼び出す場合に呼び出す関数が[[仮想関数]]であれば、サブクラスで上書きされている場合、派生クラスのメンバが呼び出されます。
vim: filetype=mediawiki
+
-->
+
  
読み方
+
'''読み方'''
 
;[[仮想関数]]: かそうかんすう
 
;[[仮想関数]]: かそうかんすう
  
行9: 行7:
  
 
== 概要 ==
 
== 概要 ==
 
 
[[仮想関数]] を用いない場合、ポインタの型のメンバが呼び出されます。
 
[[仮想関数]] を用いない場合、ポインタの型のメンバが呼び出されます。
 
[[仮想関数]] を使用したときは、派生クラスのポインタを基底クラスのポインタで受け取っても、派生クラスのメンバ関数を呼び出します。
 
[[仮想関数]] を使用したときは、派生クラスのポインタを基底クラスのポインタで受け取っても、派生クラスのメンバ関数を呼び出します。
  
== 通常の継承の例 ==
+
[[クラス]]を継承する場合には、デストラクタを[[仮想関数]]として定義する必要があります。[[継承と仮想デストラクタ]]をご参照ください。
 +
== 継承の例 ==
 +
=== ソースコード extends_0.cpp ===
 +
<syntaxhighlight lang="cpp">
 +
#include <iostream>
 +
using namespace std;
  
この例では、[[仮想関数]]を使用していません。
+
class B {
 +
        public:
 +
                void x() {
 +
                        f();
 +
                }
 +
                void f() {
 +
                        cout << __PRETTY_FUNCTION__ << endl;
 +
                }
 +
};
  
=== ソースコード extends_1.cpp ===
+
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;
 +
}
 +
</syntaxhighlight>
 +
=== コンパイル ===
 +
<syntaxhighlight lang="bash">
 +
c++  extends_0.cpp -o extends_0
 +
</syntaxhighlight>
 +
=== 実行例 ===
 +
[[クラス]] Bのx()は、f()を呼び出しています。
 +
Bを[[継承]]したC1は、f()を[[オーバーライド]]しています。
 +
 +
C1のインスタンスc1のxを呼び出すと、基底クラスのf()を呼び出しています。
 +
c1のf()を呼び出すとC1のf()が呼び出されます。
 +
 +
このように、メンバ関数を[[オーバーライド]]しても親クラスの関数に影響していません。
 +
 +
<syntaxhighlight lang="bash">
 +
% ./extends_0
 +
void B::f()
 +
void C1::f()
 +
</syntaxhighlight>
 +
== 仮想関数の例 ==
 +
親クラスの関数が呼び出している関数を[[オーバーライド]]しても、親クラスの関数を呼び出すと[[オーバーライド]]した関数を呼び出せませんでした。
 +
一部の関数の機能変更をしたいときに、これでは、たくさん書き換えないといけなくなってしまいます。
 +
 +
そこで、[[継承]]して[[オーバーライド]]して拡張しやすくするには、基底クラスのメンバ関数を[[仮想関数]]にします。
 +
=== ソースコード virtual_2.cpp ===
 +
<syntaxhighlight lang="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;
 +
}
 +
</syntaxhighlight>
 +
=== コンパイル ===
 +
<syntaxhighlight lang="bash">
 +
c++  virtual_2.cpp -o virtual_2
 +
</syntaxhighlight>
 +
=== 実行例 ===
 +
B の[[仮想関数]] f()は、C1 で[[オーバーライド]]しています。
 +
C1 は、基底クラスの x() を呼び出すと、 x()は、C1 で[[オーバーライド]]した関数を呼び出しました。
 +
 +
<syntaxhighlight lang="bash">
 +
% ./virtual_2
 +
virtual void C1::f()
 +
</syntaxhighlight>
 +
== 通常の継承の例 ==
 +
この例では、[[仮想関数]]を使用していません。
 +
=== ソースコード extends_1.cpp ===
 
<syntaxhighlight lang="cpp">
 
<syntaxhighlight lang="cpp">
 
#include <iostream>
 
#include <iostream>
行51: 行145:
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
 
 
=== コンパイル ===
 
=== コンパイル ===
 
 
<syntaxhighlight lang="bash">
 
<syntaxhighlight lang="bash">
g++  extends_1.cpp -o extends_1
+
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
行67: 行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>
行104: 行190:
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
 
 
=== コンパイル ===
 
=== コンパイル ===
 
 
<syntaxhighlight lang="bash">
 
<syntaxhighlight lang="bash">
g++  virtual_1.cpp -o virtual_1
+
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
行120: 行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()

関連項目