【電子工作番外編】Z-1.C++番外編 Baseクラスの関数に対するvirtualの必要性
こんばんわ。
SAIです。
Z-0で、デストラクタにvirtualをつける意味を説明しました。
次は、ベースクラスの機能関数に対して virtualがいるのかについて。
これに関しては、ちょっと悩ましいところです。
SAIの考えとしては、
継承させたい関数にはvirtualを付けとけ!
なのですが、理想はおそらくSAIの認識であってるんですが、
大手を振ってそうとも言えないんですよね。
意図して、virtual付けない!という選択肢もありなんです。
最も、そんなつくりは、ひねくれたコードだと思いますが。。
四の五の言っても、
結局のところ、理由や動作を理解しないとすぐ忘れちゃうと思いますので、
難しいことは後回しにして、
今回もどんな動作になるか、
実際どうなるかを見てみたほうが良いですね。
今回は、こんな形にしました。
---------------------------------
/** ベースクラス
* コンストラクタで、ベースクラスデバック用のaPinId = iPinId番目のPinをON、
* デストラクタで、ベースクラスデバック用のiPinId番目のPinをOFF
* LedOutput()が呼び出されたラ、ベースクラスデバック用のiPinId番目のPinをON/OFFする
*/
class LedBase
{
public:
LedBase(int aPinId);
virtual ~LedBase(); // ★virtualあり!
void LedOutput(bool aIo); // ★virtualなし!
private:
int iPinId;
};
/** 派生クラス
* コンストラクタで、子クラスデバック用のaLedControlId= iLedControlId番目のPinをON、
* デストラクタで、子クラスデバック用のiLedControlId番目のPinをOFF
* LedOutput()が呼び出されたら、子クラスデバック用のiPinId番目のPinをON/OFFする
*/
class LedControl :public LedBase
{
public:
LedControl(int aPinId, int aLedControlId);
~LedControl();
void LedOutput(bool aIo);
private:
int iLedControlId;
};
---------------------------------
機能関数だけではなく、
ひっそりとメンバ変数も追加しましたが、
制御IDを呼び出し側で操作できるようにしてみたというものです。
♯何度もロジアナで絵を撮るのが面倒だともいう。
①このクラス構成で、LedControlクラスのLedOutput()を呼び出ししたらどうなるでしょうか?
/* 最初は、子クラスでコントロールする */
LedControl* lControl = new LedControl(13,12);
lControl->LedOutput(false);
lControl->LedOutput(true);
delete lControl;
lControl = NULL;
②またこのクラス構成で、LedControlクラスをベースクラス型でLedOutput()を呼び出ししたらどうなるでしょうか?
/* 次は、親クラスでコントロールする */
LedBase* lBase = new LedControl(11,10);
lBase->LedOutput(false);
lBase->LedOutput(true);
delete lBase ;
lBase = NULL;
結果はこんな感じ
↓childが処理 ↓Baseが処理
子クラスの型でLedOutput()を呼び出した時は、
子クラスのLedOutput()が呼び出されていますが、
親クラスのLedOutput()は呼び出されません。
virtualがついていないと、
親クラスの型でLedOutput()を呼び出した時は、
子クラスのLedOutput()が呼び出されませんが、
親クラスのLedOutput()が呼び出されています。
これを、期待して実装するかどうかです。
ちなみに、呼び出し側で子クラスの型にキャストしてやれば、
子クラスのLedOutput()が呼び出されています。
それでは本題。
親クラスのLedOutput()関数にvirtualを付けてあげるとどうなるでしょうか?
結果はこんな感じに変わります。
↓childが処理 ↓childが処理
子クラスの型であろうと、親クラスの型であろうと、LedOutput()を呼び出した時は、
子クラスのLedOutput()が呼び出されていますが、
親クラスのLedOutput()は呼び出されません。
デストラクタ以外の機能関数は、virtualを付けた場合、親クラスの代わりに子クラスが呼ばれる!
という処理になります。
SAIの理解では、virtualというマークがあれば、
子クラスに派生した関数があるかもしれないよ!
通常の関数の場合は、子クラスを優先して呼び出してね!
というキーワードになります。
親は必ずしも呼ばれないんです。
では、
どうしても親クラスを呼びたいときはどうするのか?
呼び出す側で関数の型をベースクラスとして、指定してやれば呼び出せはします。
↓
(LedBase) lBase->LedBase::LedOutput(false);
または、
子クラスの関数の中で、
LedBase::LedOutput(false);
を呼び出せば子クラスの関数の処理の途中で、
任意のタイミングで親クラスも処理できます。
なぜ子クラスだけ呼ばれるのでしょうか?
おそらく、
ベースクラスの処理をしたくないときは、
子クラスの機能関数を空にすることで、
明示的に処理を省く!
という思想だと考えています。
例えば、Windowsの描画処理で、
ベースクラスでは右上の[x]閉じる を表示する処理があるものに対して、
閉じられたくない場合は、[x]閉じる を表示する関数を、空関数で実装すると
親クラスの[x]閉じるの表示処理が行われない
といったイメージ。
ただ、ひねくれた人は、
ベースクラスで処理するときはベースクラス呼び出してください。
ということもあるかもしれません。
♯SAIに言わせたら、↓こうしろですけど。
♯ (LedBase) lBase->LedBase::LedOutput(false);
というわけで、
SAIの考えとしては、
継承させたい関数にはvirtualを付けとけ!
となるわけです。
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
雑談
子クラスのコンストラクタで、親クラスのコンストラクタを呼び出す必要があるの?
と疑問を持たれた方がいたので、
今回はわざと、親クラスのコンストラクタと子クラスのコンストラクタの
引数を変えて、説明しやすくしてみましたよ。
親クラス
LedBase::LedBase(int aPinId);
子クラス
LedControl::LedControl(int aPinId, int aLedControlId);
別に、親クラスと子クラスって引数の数や役割が違ってもいいのです。
むしろ子クラスには引数がなくったって別にいいのです。
肝心なのは、子クラスがどんな情報を親クラスに渡したいかです!
そして、子クラスより先に親クラスが呼び出されます。
例えば、今回は第一引数を親クラスに渡す ↓ としていますが
----------------------
/** 子クラスのコンストラクタ */
LedControl::LedControl(int aPinId, int aLedControlId)
:LedBase(aPinId)
----------------------
子クラスの固有IDを渡すとかでもいいんですよ。
----------------------
/** 子クラス①のコンストラクタ */
LedControl1::LedControl1()
:LedBase(1)
{
}
/** 子クラス②のコンストラクタ */
LedControl2::LedControl2()
:LedBase(2)
{
}
----------------------
ちなみに、親クラスのコンストラクタに引数がない場合についてですが、
Z-0の動作を見るとわかると思いますが、
なにも書かなくても暗黙で親クラスのコンストラクタが呼び出されます。
逆に親クラスに引数があるのに、
子クラスのコンストラクタから親クラスのコンストラクタを呼び出さないと、
コンパイルエラーになります。
これ、雑談にしましたが、項にしてもいい内容だったかもしれませんね。
雑談でした。
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
本日使ったコードは以下の通りです!
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
/** ベースクラス
* コンストラクタで、ベースクラスデバック用のaPinId = iPinId番目のPinをON、
* デストラクタで、ベースクラスデバック用のiPinId番目のPinをOFF
* LedOutput()が呼び出されたラ、ベースクラスデバック用のiPinId番目のPinをON/OFFする
*/
class LedBase
{
public:
LedBase(int aPinId);
virtual ~LedBase(); // ★virtualあり!
// void LedOutput(bool aIo); // ★virtualなし!
virtual void LedOutput(bool aIo); // ★virtualあり!
private:
int iPinId;
};
/** 派生クラス
* コンストラクタで、子クラスデバック用のaLedControlId= iLedControlId番目のPinをON、
* デストラクタで、子クラスデバック用のiLedControlId番目のPinをOFF
* LedOutput()が呼び出されたら、子クラスデバック用のiPinId番目のPinをON/OFFする
*/
class LedControl :public LedBase
{
public:
LedControl(int aPinId,int aLedControlId);
~LedControl();
void LedOutput(bool aIo);
private:
int iLedControlId;
};
/** ベースクラスのコンストラクタ */
LedBase::LedBase(int aPinId)
:iPinId(aPinId)
{
delay(100);
// 出力設定 //
pinMode(iPinId, OUTPUT);
// ベースクラスの生命PIN制御 //
digitalWrite(iPinId, HIGH); // IO OFF
}
/** ベースクラスのデストラクタ */
LedBase::~LedBase()
{
delay(100);
// ベースクラスの生命PIN制御 //
digitalWrite(iPinId, LOW); // IO OFF
}
/** ベースクラスのIO制御 */
void LedBase::LedOutput(bool aIo)
{
if(aIo)
{
digitalWrite(iPinId, HIGH); // IO OFF
}else{
digitalWrite(iPinId, LOW); // IO OFF
}
}
/** 子クラスのコンストラクタ */
LedControl::LedControl(int aPinId,int aLedControlId)
:LedBase(aPinId)
,iLedControlId(aLedControlId)
{
delay(100);
// 出力設定 //
pinMode(iLedControlId, OUTPUT);
// ベースクラスの生命PIN制御 //
digitalWrite(iLedControlId, HIGH); // IO OFF
}
/** 子クラスのデストラクタ */
LedControl::~LedControl()
{
delay(100);
// ベースクラスの生命PIN制御 //
digitalWrite(iLedControlId, LOW); // IO OFF
}
/** 子クラスのIO制御 */
void LedControl::LedOutput(bool aIo)
{
if(aIo)
{
digitalWrite(iLedControlId, HIGH); // IO OFF
}else{
digitalWrite(iLedControlId, LOW); // IO OFF
}
// ベースクラスの処理もする。
LedBase::LedOutput(false);
}
/* setup関数 */
void setup() {
// put your setup code here, to run once:
}
/* Loop関数 */
void loop() {
// put your main code here, to run repeatedly:
/* 最初は、子クラスでコントロールする */
LedControl* lControl = new LedControl(13,12);
delay(1000);
lControl->LedOutput(false);
delay(100);
lControl->LedOutput(true);
delay(200);
delete lControl;
lControl = NULL;
delay(2000);
/* 次は、親クラスでコントロールする */
LedBase* lBase = new LedControl(11,10);
delay(1000);
lBase->LedOutput(false);
delay(100);
lBase->LedOutput(true);
delay(200);
delete lBase ;
lBase = NULL;
delay(5000);
}
//ちなみに、baseを呼びたいとき
// lBase->LedBase::LedOutput(false);









