【電子工作番外編】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);



 

【電子工作番外編】Z-0.C++番外編 Baseクラスのデストラクタのvirtual記載必要性

 

こんばんわ。

SAIです。

 

C++を有効に活用するには、継承をうまく使う必要があります。

この継承ですが、うまく使えばとっても良い機能なのですが、

しっかり理解せずにベースクラスを設計すると、解放漏れや意図しない呼び出しにつながります。


この継承について、よくわからないという疑問を持った方がいたので、

実験とあわせて解説します。

 

 

今回は、ベースクラスのデストラクタvirtual を付ける必要はあるのか?

ベースクラスのコンストラクタ、デストラクタの呼び出される順と合わせて

考えてみましょう。

 

※デストラクタ以外のvirtual関数はちょっと動きが違うので、別途説明します。

 (デストラクタ以外は動作確認ですぐ発見できますが、デストラクタはやばいので先に実験です。)

 

 

簡単に回答するなら、

継承という機能を有効に使うなら、virtualを付けてあげるべきです。

 

厳密には色々ありますが、SAIが規約を作るなら、

ベースクラスのデストラクタにはvirtualを付けろ!

です。

 

ただ、おそらく理由を理解しないとすぐ忘れます。

 

では、ベースクラスの関数、特にデストラクタにvirtualなければどうなるか、

難しいことは後回しにして、

実際どうなるかを見てみたほうが良いですね。

 

 

---------------------------------

/** ベースクラス

 *  コンストラクタで、ベースクラスデバック用の13番PinをON、

 *  デストラクタで、ベースクラスデバック用の13番PinをOFF

 */
class LedBase
{
public:
  LedBase();
  ~LedBase();                  // ★virtualなし!
};

 

/** 派生クラス 

* コンストラクタで、子クラスデバック用の12番PinをON、

* デストラクタで、ベースクラスデバック用の12番PinをOFF

*/
class LedControl :public LedBase
{
public:
  LedControl();
  ~LedControl();
};

---------------------------------

 

このクラス構成で、LedControl をnewおよびdeleteしたらどうなるでしょうか?

 

単純に考えたら、

子クラスをNewしたら、13番Pinが光って、12番ピンが光り、

子クラスをdeleteしたら、12番Pinが消えて、13番ピンが消えます。

 

 

これを以下のような、子クラスのインスタンスとして制御をするとどうなるでしょうか?

/* 子クラスとして制御 */

  LedControl* lControl = new LedControl();
  delay(1000);
  delete lControl;
  lControl = NULL;
  delay(2000);

 

          ↑new              ↑delete

newしたとき

親クラスのコンストラクタ

子クラスのコンストラクタ

 

deleteしたとき

子クラスのデストラクタ

親クラスのデストラクタ

 

の順で呼び出されました。

 

いいですね。

 

 

さて、

ベースクラスを作るということは、派生クラスを沢山作ることになります。

ボタンAの子クラス、ボタンBの子クラスといったっ感じ。

 

ただ、実処理としては、

呼び出しをする側は、これらを各子クラスとして呼び出しをするのは

処理的にも単調で美しくないです。

仮に各子クラスをA~Dとすると以下のような形になります。

 

生成するときは

  LedControlA* lControlA = new LedControlA();
  LedControlB* lControlB = new LedControlB();
  LedControlC* lControlC = new LedControlC();
  LedControlD* lControlD = new LedControlD();
 

機能を使うときは

  lControlA ->Func();
  lControlB->Func();
  lControlC->Func();
  lControlD->Func();

 

deleteするときは、
  delete lControlA;

  delete lControlB;

  delete lControlC;

  delete lControlD;

 

それぞれ一つ一つ呼ばなくてはならず、美しくないです。

 

ではどう制御するか?

 

ベースクラスの制御する側は基本的に親クラス型で管理するほうが、

美しく可読性もいいです。

 

また、設計変更により機能を追加した場合にも、

修正箇所が生成処理の部分を追加すればOKで、

機能関数の呼び出し箇所に対しては、汎用処理なら

修正しなくても既存の処理のままでOKとなります。

 

生成するときは

const int ClassMax =4;

LedBase* lLedList[ClassMax];

lLedList[0] = new LedControlA();

lLedList[1] = new LedControlB();

lLedList[2] = new LedControlC();

lLedList[3] = new LedControlC();

 

機能を使うときは

for(int i = 0 ; ClassMax >i ; i++)

{

lLedList[i]->Func();

}

deleteするときは、

for(int i = 0 ; ClassMax >i ; i++)

{

delete lLedList[i];

lLedList[i] = null;

}

 

こんな感じです。

 

さて、ここで問題になるのが、deleteしているのは親クラス型であること。

 

先ほどのクラスを、親クラス型として制御をするとどうなるでしょうか?

 

 

/* 次は、親クラスでコントロールする */
  LedBase* lBase = new LedControl();
  delay(1000);
  delete lBase ;
  lBase = NULL;
  delay(5000);
 

どうなったでしょうか?

          ↑new              ↑delete

newしたとき

親クラスのコンストラクタ

子クラスのコンストラクタ

 

deleteしたとき

   ③子クラスのデストラクタはよびだされません!

親クラスのデストラクタ

 

の順で呼び出されました。

 

はい、③子クラスの解放漏れ発生です。

 

 

では、もしベースクラスのデストラクタにvirtualの記載があると、

どうなるでしょうか?

先ほどのクラス宣言の

// ★virtualなし!

 

のところに、virtualを付けてみましょう。


          ↑new              ↑delete

 

 

newしたとき

親クラスのコンストラクタ

子クラスのコンストラクタ

 

deleteしたとき

子クラスのデストラクタ

親クラスのデストラクタ

 

の順で呼び出されました。

 

というわけで、子クラスにはvirtualが必要になるわけです。

 

 

では、どういう仕組みでこんな動作になるのでしょうか?

 

 

ここで、クラスの構成を理解する必要が出てきます。

 

クラス図にするとこんな感じですね。

 

最初に行ったように子クラス型で呼び出しを行うと、

4つの関数すべてが見えています。

 

ちょっと極端なイメージですが、以下のような関数軍があるような感じです。

(イメージです)

 

ところが、親クラス型で呼び出しを行う場合、

子クラスの関数が見えません。

 

ま、includeを考えたらわかりそうなもんですが、

Baseで操作するクラスはBaseクラスしかIncludeしていないので、

派生クラスにどんな関数が追加されているかなんて知ったこっちゃないんです。

 

 

しかし、そんなことを言い出したら、

子クラスの関数を呼び出せないじゃん!

継承を使う意味ないじゃん

ということになります。

 

 

そこで活躍するのが、virtualです。

 

SAIの理解では、virtualというマークがあれば、

 

子クラスに派生した関数があるかもしれないよ!

というキーワードになります。

 

つまり、

(イメージです)

 

こんな感じ。

デストラクタが呼び出されたら、

子クラスにもデストラクタがあるかもしれない!

 

ということになるわけです。

 

#ただ、通常のvirtual関数と違い、ベースクラスのデストラクタも呼び出されるところが特殊ですね。

 

 

じゃぁ、ベースクラスは全部の関数にvirtualを付けたらいいじゃん!

とはなりません

 

virtualを付けると、

派生クラスに関数があるかも!ということで派生クラスから関数を探す処理?

みたいなのが挟まるらしく、

ちょっと処理コストが高いらしいです。

 

 

以下二つが成立したときのみvirtualをつけるのが本当は正しいのだとおもいます。

 

①オーバーライドしていい関数+デストラクタ

②派生クラスを使う人が、ベースクラス型で呼び出す可能性がある

 

 

 

 

晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ

雑談

 

とはいうものの、実際のところはうっかりさんがいるものです。

 

virtualがついていないのに、ベースクラス型で操作したり、

とある関数のみvirtualを付けるのを忘れたり、

という凡ミスで不具合を発生させることがありますので、

 

ベースクラスで、派生する可能性のある関数は全部virtualつけるという規約の方が、

不具合が起きにくいのかもしれません。

 

そしてもう一つ、

コンストラクタにvirtualは必要ないのか?

 

→必要ありません。

 

だって、コンストラクタってクラス生成時しか呼び出されませんよね。

では、クラスの生成を親クラスの型で行なわれたらどうなるか?

それはもはや子クラスではないです。

 

というわけで、親クラス型で子クラスを呼ぶようなことはあり得ないんで、不要となるものと考えています。

 

晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ晴れ

 

SAIでした。

 

本日使ったコードは以下の通りです!

 

宇宙人しっぽ宇宙人からだ宇宙人からだ宇宙人からだ宇宙人からだ宇宙人からだ宇宙人からだ宇宙人からだ宇宙人からだ宇宙人からだ宇宙人からだ宇宙人からだ宇宙人からだ宇宙人からだ宇宙人からだ宇宙人あたま

const int CBASE_PIN_ID = 13;
const int CCHILD_PIN_ID = 12;

/** ベースクラス */
class LedBase
{
public:
  LedBase();
  virtual ~LedBase();               // virtualあり!
//  ~LedBase();               // virtualなし!
};

/** 派生クラス */
class LedControl :public LedBase
{
public:
  LedControl();
  ~LedControl();
};


/** ベースクラスのコンストラクタ */
LedBase::LedBase()
{
  delay(100);
  // 出力設定 //
  pinMode(CBASE_PIN_ID, OUTPUT); 
  // ベースクラスの生命PIN制御 //
  digitalWrite(CBASE_PIN_ID, HIGH);    // IO OFF 
}

/** ベースクラスのデストラクタ */
LedBase::~LedBase()
{
  delay(100);
  
  // ベースクラスの生命PIN制御 //
  digitalWrite(CBASE_PIN_ID, LOW);    // IO OFF 
}

/** 子クラスのコンストラクタ */
LedControl::LedControl()
{
  delay(100);
  
  // 出力設定 //
  pinMode(CCHILD_PIN_ID, OUTPUT); 
  // ベースクラスの生命PIN制御 //
  digitalWrite(CCHILD_PIN_ID, HIGH);    // IO OFF 
}

/** 子クラスのデストラクタ */
LedControl::~LedControl()
{
  delay(100);
  
  // ベースクラスの生命PIN制御 //
  digitalWrite(CCHILD_PIN_ID, LOW);    // IO OFF 
}


/* Loop関数 */
void loop() {
  // put your main code here, to run repeatedly:

  /* 最初は、子クラスでコントロールする */
/*  
   
  LedControl* lControl = new LedControl();
  delay(1000);
  delete lControl;
  lControl = NULL;
  
  delay(2000);
*/
  /* 次は、親クラスでコントロールする */
  LedBase* lBase = new LedControl();
  delay(1000);
  delete lBase ;
  lBase = NULL;
  
  delay(5000);

}

/* setup関数 */
void setup() {
  // put your setup code here, to run once:

}
 

【電子工作番外編】A-0.ArduinoでC++言語で操ろう

 

Arduinoの学習の途中ですが、

 

ちょっと横道へそれます。

 

というか、番外編を挟みます。

 

Arduinoは、C++言語が使えるらしいのです。

 

それならば、併せてC++言語の勉強をしようというお話です。

 

 

C言語は手続き型の言語ですが、

C++言語はオブジェクト指向の言語ですね。

 

 

今後遊んでいくうえで、

ソースコードの使いまわしを行うことを考えると、

 

グローバル変数に左右されるC言語より、

クラス内に包括できるC++言語の方が流用性は高いはずです。

 

(もっとも、Arduinoはプロトタイプ宣言が不要なので、

 意外と何もなく使いまわせるかもしれないですけど。)

 

というわけで、

主流ラインからそれるので、

タイトルを4ではなくAとさせてもらいました。