Cpp クラス 仮想関数 新しいページはコチラ
提供: yonewiki
(→クラス 仮想関数) |
|||
5行: | 5行: | ||
<tr> | <tr> | ||
<td style="width:1px;"></td> | <td style="width:1px;"></td> | ||
− | <td class="mbox-text plainlist" style="">本来の表記は「<b><span id="RealTitle" style="font-size:large;">C++ クラス 仮想関数</span></b>」です。この記事に付けられた題名は{{記事名の制約}}から不正確なものとなっています。</td> | + | <td class="mbox-text plainlist" style="">本来の表記は「<b><span id="RealTitle" style="font-size:large;">C++(Cpp) クラス 仮想関数</span></b>」です。この記事に付けられた題名は{{記事名の制約}}から不正確なものとなっています。</td> |
</tr> | </tr> | ||
</table> | </table> | ||
11行: | 11行: | ||
<br /> | <br /> | ||
== '''クラス 仮想関数''' == | == '''クラス 仮想関数''' == | ||
− | + | 前の項目の[[Cpp クラス 継承 アップキャスト]]で、デストラクタが呼ばれず、プログラムがメモリリークを起こすような怪しい終わり方になっていました。その対策方法を「継承 デストラクタ」の項目で説明するつもりなのですが、対策をするには、この仮想関数という仕組みを理解する必要があります。 | |
− | + | ざっくり言うと、基底クラス側で、派生クラスでも同じ名前の関数を作ってもいいよ。っていうメカニズムです。今回作った継承のサンプルだと基底クラスの機能があまりにも少ないので、基底クラスに仮想関数を設定しにくいので、もう少し肉付けしてみます。 | |
+ | いい感じのサンプルになるかな?例えば、これまでに作ったプログラムでは、アップキャストしても基底クラスで保有している関数しか使えないため、基本料金の合計(月額料金×利用月数)と全体の合計金額しか表示することが出来ていませんでした。オプション料金が発生したときは、ちゃんとオプション料金がいくらだったのかを表示してほしいとします。そうすると、「基本料金、合計金額」という出力ではなく、「基本料金、オプション明細、合計金額」というような出力をする関数が欲しいところです。この仕組みを実現するために、まずは基底クラスにvoid mfVirtual_vDispValue();という関数を追加します。 | ||
+ | |||
+ | |||
+ | <span style="color: #ffffff; background-color: #555555; padding: 0px 5px 0px 5px; display: inline-block;">cpp <span>(</span>基底クラス BaseInheritance.h<span>)</span><!-- padding 上 右 下 左--> | ||
+ | <syntaxhighlight2 lang="cpp" line> | ||
+ | #ifndef __BASEINHERITANCE_H_YONET__ | ||
+ | #define __BASEINHERITANCE_H_YONET__ | ||
+ | #if _MSC_VER > 1000 | ||
+ | #pragma once | ||
+ | #endif | ||
+ | |||
+ | class CBaseInheritance { | ||
+ | protected: | ||
+ | int m_iBaseMoney = 0; | ||
+ | int m_iBaseMonth = 0; | ||
+ | int m_iMoney = 0; | ||
+ | int mf_iBaseSumMoney(); | ||
+ | public: | ||
+ | CBaseInheritance(); | ||
+ | CBaseInheritance(int iArgBaseMoney, int iArgBaseMonth); | ||
+ | ~CBaseInheritance(); | ||
+ | void mfVirtual_vDispValue(); | ||
+ | void mf_vBaseDispValue(); | ||
+ | }; | ||
+ | </syntaxhighlight2> | ||
+ | |||
+ | で、まずは、基底クラスとしての、オプション明細つきとして、Option="None"と表示する以下のようなプログラムとしてみます。 | ||
+ | |||
+ | <span style="color: #ffffff; background-color: #555555; padding: 0px 5px 0px 5px; display: inline-block;">cpp <span>(</span>基底クラス BaseInheritance.cpp<span>)</span><!-- padding 上 右 下 左--> | ||
+ | <syntaxhighlight2 lang="cpp" highlight="122-124" line start=101 > | ||
+ | #include "pch.h" | ||
+ | #include "BaseInheritance.h" | ||
+ | |||
+ | CBaseInheritance::CBaseInheritance() { | ||
+ | printf("Constructor:CBaseInheritance()\n"); | ||
+ | } | ||
+ | |||
+ | CBaseInheritance::CBaseInheritance(int iArgBaseMoney, int iArgBaseMonth) | ||
+ | :m_iBaseMoney(iArgBaseMoney) | ||
+ | ,m_iBaseMonth(iArgBaseMonth) | ||
+ | ,m_iMoney(0) { | ||
+ | printf("Constructor:CBaseInheritance(int,int)\n"); | ||
+ | } | ||
+ | |||
+ | CBaseInheritance::~CBaseInheritance() { | ||
+ | printf("Destructor:~CBaseInheritance()\n"); | ||
+ | } | ||
+ | |||
+ | int CBaseInheritance::mf_iBaseSumMoney() { | ||
+ | m_iMoney = m_iBaseMoney * m_iBaseMonth; | ||
+ | return m_iMoney; | ||
+ | } | ||
+ | |||
+ | void CBaseInheritance::mfVirtual_vDispValue() { | ||
+ | int iSumMoney = mf_iBaseSumMoney(); | ||
+ | printf("BaseMoney=%d, option=\"none\" total=%d\n", iSumMoney,m_iMoney); | ||
+ | } | ||
+ | |||
+ | void CBaseInheritance::mf_vBaseDispValue() { | ||
+ | int iSumMoney = m_iBaseMoney * m_iBaseMonth; | ||
+ | printf("BaseMoney=%d, total=%d\n", iSumMoney, m_iMoney); | ||
+ | } | ||
+ | </syntaxhighlight2> | ||
+ | |||
+ | これで以下のようなメイン関数を作って | ||
+ | |||
+ | <span style="color: #ffffff; background-color: #555555; padding: 0px 5px 0px 5px; display: inline-block;">cpp <span>(</span>メイン関数 InheritanceMain.cpp<span>)</span><!-- padding 上 右 下 左--> | ||
+ | <syntaxhighlight2 lang="cpp" line start=201> | ||
+ | #include "pch.h" | ||
+ | #include "BaseInheritance.h" | ||
+ | #include "DeriveInheritance.h" | ||
+ | #include "DeriveArrInheritance.h" | ||
+ | #include "DeriveDiscountInheritance.h" | ||
+ | |||
+ | CBaseInheritance* pCBaseInheritanceUpcastF(int iSelectDerive) { | ||
+ | |||
+ | CDeriveInheritance* pCDeriveInheitance; | ||
+ | pCDeriveInheitance = new CDeriveInheritance(1900, 12); | ||
+ | pCDeriveInheitance->mf_vDeriveSetOptionValue(900); | ||
+ | return pCDeriveInheitance; | ||
+ | }; | ||
+ | |||
+ | int main(int iArgSize, char* pcArgArr[]) { | ||
+ | |||
+ | CBaseInheritance objCBaseIneritance(1900, 12); | ||
+ | objCBaseIneritance.mfVirtual_vDispValue(); | ||
+ | |||
+ | CBaseInheritance* pCBaseInheritanceUpcast; | ||
+ | pCBaseInheritanceUpcast = pCBaseInheritanceUpcastF(1); | ||
+ | pCBaseInheritanceUpcast->mfVirtual_vDispValue(); | ||
+ | delete pCBaseInheritanceUpcast; | ||
+ | |||
+ | return 0; | ||
+ | |||
+ | } | ||
+ | </syntaxhighlight2> | ||
+ | |||
+ | 実行すると | ||
+ | |||
+ | <span style="color: #ffffff; background-color: #555555; padding: 0px 5px 0px 5px; display: inline-block;">実行結果<!-- padding 上 右 下 左--> | ||
+ | <syntaxhighlight2 lang="text" line start=301> | ||
+ | Constructor:CBaseInheritance(int,int) | ||
+ | BaseMoney=22800, option="none" total=22800 | ||
+ | Destructor:~CBaseInheritance() | ||
+ | </syntaxhighlight2> | ||
+ | |||
+ | と、なってオプション明細表示機能を基底クラスに持たせることができました。この関数を派生クラスでも同じように持たせれば、同じ機能を作れるのですが、同じ関数がある場合でも、派生クラスの方の関数が呼ばれて欲しいところです。同じ機能だから同じ名前を使うというのは、やっていいことです。ですが、アップキャストされた場合は、基底クラスの方にある関数を呼び出してしまいます。元の機能を優先して呼び出してほしいです。派生クラスの関数なんて基本クラスは知る由もないはずですが、そのジレンマみたいなものを解決するのが、仮想関数という機能です。 | ||
+ | |||
+ | |||
+ | ちなみにアップキャストの記事で使ったプログラムのメイン関数だけを以下のように替えて確かめるとすると、 | ||
+ | |||
+ | |||
+ | <span style="color: #ffffff; background-color: #555555; padding: 0px 5px 0px 5px; display: inline-block;">cpp <span>(</span>メイン関数 InheritanceMain.cpp<span>)</span><!-- padding 上 右 下 左--> | ||
+ | <syntaxhighlight2 lang="cpp" line start=401> | ||
+ | #include "pch.h" | ||
+ | #include "BaseInheritance.h" | ||
+ | #include "DeriveInheritance.h" | ||
+ | #include "DeriveArrInheritance.h" | ||
+ | #include "DeriveDiscountInheritance.h" | ||
+ | |||
+ | CBaseInheritance* pCBaseInheritanceUpcastF(int iSelectDerive) { | ||
+ | |||
+ | CDeriveInheritance* pCDeriveInheitance; | ||
+ | pCDeriveInheitance = new CDeriveInheritance(1900, 12); | ||
+ | pCDeriveInheitance->mf_vDeriveSetOptionValue(900); | ||
+ | return pCDeriveInheitance; | ||
+ | }; | ||
+ | |||
+ | int main(int iArgSize, char* pcArgArr[]) { | ||
+ | |||
+ | CDeriveInheritance objCDeriveIneritance(1900, 12, 900); | ||
+ | objCDeriveIneritance.mfVirtual_vDispValue(); | ||
+ | |||
+ | CBaseInheritance* pCBaseInheritanceUpcast; | ||
+ | pCBaseInheritanceUpcast = pCBaseInheritanceUpcastF(1); | ||
+ | pCBaseInheritanceUpcast->mfVirtual_vDispValue(); | ||
+ | delete pCBaseInheritanceUpcast; | ||
+ | |||
+ | return 0; | ||
+ | |||
+ | } | ||
+ | </syntaxhighlight2> | ||
+ | |||
+ | となって、実行結果は | ||
+ | |||
+ | <span style="color: #ffffff; background-color: #555555; padding: 0px 5px 0px 5px; display: inline-block;">実行結果<!-- padding 上 右 下 左--> | ||
+ | <syntaxhighlight2 lang="text" line start=501> | ||
+ | Constructor:CBaseInheritance(int,int) | ||
+ | Constructor:CDriveInheritance(int,int,int) | ||
+ | BaseMoney=22800, OptionMoney=600 total=23400 | ||
+ | Constructor:CBaseInheritance(int,int) | ||
+ | Constructor:CDriveInheritance(int,int,int) | ||
+ | BaseMoney=22800, option="none" total=23400 | ||
+ | Destructor:~CDriveInheritance() | ||
+ | Destructor:~CBaseInheritance() | ||
+ | </syntaxhighlight2> | ||
+ | |||
+ | という具合です。アップキャストした方が派生クラスの関数がわからないので、基底クラスの方の関数が呼ばれています。オプションがいくらかあって、基本料金と合計金額に差があるのに、optionは知らんみたいになって残念な感じです。で、これを解決するのが、仮想関数だというキーワードを出していました。仮想関数という機能を使うには基底クラスが仮想関数の存在を認める宣言をするだけです。 | ||
+ | |||
+ | |||
+ | 具体的に以下のような基底クラスのヘッダファイル宣言にするだけです。 | ||
+ | |||
+ | <span style="color: #ffffff; background-color: #555555; padding: 0px 5px 0px 5px; display: inline-block;">cpp <span>(</span>基底クラス BaseInheritance.h<span>)</span><!-- padding 上 右 下 左--> | ||
+ | <syntaxhighlight2 lang="cpp" line start=601> | ||
+ | #ifndef __BASEINHERITANCE_H_YONET__ | ||
+ | #define __BASEINHERITANCE_H_YONET__ | ||
+ | #if _MSC_VER > 1000 | ||
+ | #pragma once | ||
+ | #endif | ||
+ | |||
+ | class CBaseInheritance { | ||
+ | protected: | ||
+ | int m_iBaseMoney = 0; | ||
+ | int m_iBaseMonth = 0; | ||
+ | int m_iMoney = 0; | ||
+ | int mf_iBaseSumMoney(); | ||
+ | public: | ||
+ | CBaseInheritance(); | ||
+ | CBaseInheritance(int iArgBaseMoney, int iArgBaseMonth); | ||
+ | ~CBaseInheritance(); | ||
+ | virtual void mfVirtual_vDispValue(); | ||
+ | void mf_vBaseDispValue(); | ||
+ | }; | ||
+ | #endif | ||
+ | </syntaxhighlight2> | ||
+ | |||
+ | 617行目の | ||
+ | |||
+ | <syntaxhighlight2 lang="cpp"> | ||
+ | virtual void mfVirtual_vDispValue(); | ||
+ | </syntaxhighlight2> | ||
+ | |||
+ | ようにvirtualというキーワードを宣言の部分に書けば、仮想関数という扱いになり、基底クラスでは、存在しないかもしれない架空の関数としての振る舞いになり、アップキャストされている場合は派生クラスに同じ名前の関数があるはずだから、それを見に行くようになります。この存在しないかもしれない架空の扱いを仮想関数と呼んでいるのだと思います。基底クラスでは、ある機能が自分自身にも派生クラスにもあるべきだという意味で使われます。今回の場合は、それぞれのクラス、基底クラス、派生クラスの全てに独自の合計金額を出力する関数があって、それぞれで動きは違うけど、同じ目的をもった関数が作られています。仮想関数はこういう使い方をします。実際のプログラミングでは、このサンプルよりも驚くべき使われ方や画期的な使われ方がされていることの方が多いです。 | ||
+ | |||
+ | |||
+ | そうすると、virtualをつけた場合の動作は | ||
+ | |||
+ | <span style="color: #ffffff; background-color: #555555; padding: 0px 5px 0px 5px; display: inline-block;">実行結果<!-- padding 上 右 下 左--> | ||
+ | <syntaxhighlight2 lang="text" line start=701> | ||
+ | Constructor:CBaseInheritance(int,int) | ||
+ | Constructor:CDriveInheritance(int,int,int) | ||
+ | BaseMoney=22800, OptionMoney=600 total=23400 | ||
+ | Constructor:CBaseInheritance(int,int) | ||
+ | Constructor:CDriveInheritance(int,int,int) | ||
+ | BaseMoney=22800, OptionMoney=600 total=23400 | ||
+ | Destructor:~CBaseInheritance() | ||
+ | Destructor:~CDriveInheritance() | ||
+ | Destructor:~CBaseInheritance() | ||
+ | </syntaxhighlight2> | ||
+ | |||
+ | |||
+ | となります。が、ここでも、まだ、デストラクタの数がコンストラクタの数と一致せず、まだまだ残念な感じになっています。 | ||
+ | |||
+ | |||
+ | |||
+ | でも、これで、継承 デストラクタの記事への下準備ができました。答えはもうわかってると思いますけど… | ||
[[C PlusPlus#C++からの技術|C++]]に戻る | [[C PlusPlus#C++からの技術|C++]]に戻る |