みなさんこんにちわ、こんばんわ

SAIです。

 

さて、前回はC++言語で純粋仮想関数を利用した定期呼び出しを実現しました。

 

 

さてさて、今回は非同期コールバックを実装しようと思います。

(クラス自体は4月に作ったやつです。。放置しすぎました)

 

 

何をやりたいかというと、

C言語の時もやりましたね。

 

 

 

これをC++で実装です。

 

内容の思い出しですが、

やりたかったことはこれです!

A君とB君、二人以上のタイマーを管理させる

 

 

 

 

感のいい人はお気づきでしょう。

 

タイマークラスを作成しますよ!

 

まずは、コールバックをするためには、

C++では純粋仮想関数を決めないといけませんね。

 

時間経過を示す名称になりますから、

class MTimerObserver
{
public:
  // コールバック用の純粋仮想関数 /
  virtual void OnTimerFire() = 0;
};

こんな感じにしてみたよ!

 

タイマが完了したら、

OnTimerFire() ;

が呼び出されるということですね。

 

次、管理するクラスを作るよ!

C言語だと、inoファイルにセット関数やキャンセル関数を用意していたよね。

 

今回はC++言語なので、クラスにしちゃいますよ!

 

 

 

さて、クラスを作るにあたり、一つ課題があります。

 

タイマーってどのクラスからも、すぐに使えたほうが便利ですよね?

 

 

C言語って、すべての関数はstaticな領域に格納されています。

 

では、C++言語はどうかというと、わざとstaticと書かなければ、

クラスのアドレス(ポインター)を知らないと使えないよね。


ということは、クラスのポインターを知らないクラスからは、

タイマークラスのSet関数を呼び出すことができないということになってしまいます。

 

それでは困りますね。

 

 

そんな時の対策として

 

シングルトンオブジェクト

 

という考え方があります。

 

シングルトン・・・・独身・単独でも生きていける人、自立といったような意味がありますね。

 

一人きりでも生きていける、世の中に一人しか存在しない。

といったような意味合いなのだと思います。

 

 

どういった技法かというと、

一度作ったクラスを一般公開するようなイメージです。

 

通常クラスはnewでヒープにクラスを展開します。

ところが、ヒープ上のアドレスを知っているのは、クラスを生成した人だけ。

 

シングルトンはというと、

自身で自信を作り、staticな領域にアドレスを置いておくことで、

Get関数で、アドレスを入手してどこのクラスからもアクセス用にできます。

 

 

シングルトンはどんな機能に適しているでしょうか?

 

どこからでもアクセスしたい機能

例えば、

・デバックログ関連

・タスク操作系

・タイマー管理系

・セッション管理系

・セマフォ関連

のような、どこからでもアクセスしたいようなクラスに適しています。

 

 

逆に適さない機能は、

クラスを複製が必要となるような歯車のような機能には適していません。

例えば、

状態を管理するオセロの石のようなクラス

ですね。

 

 

気を付ける点としては、

シングルトンは、

誰からもアクセスできる=誰も管理していない

ととらえることができます。

 

つまり、気を付けるのは解放漏れです。

 

必ず、一番最後に使う人にdeleteしてもらう必要があります。

が、組み込みの場合は電池がなくなるまで作りっぱなしでも

問題になることはないでしょうね。

 

 

それでは、実際に作ってみましょう。

 

/*
 * @file    CTimerOneManager
 * @brief   Lesson B_3 タイマーマネージメントクラス。
 * @author  SAI
 * @date    2021/04/04
 * @note    タイマー登録してコールバック制御するマネージメントクラス
 */
class CTimerOneManager
{
public:
  /* タイマー登録配列用クラス */
  class TObserverStr
  {
  public:
    MTimerObserver* iTimerObserver;
    long iFierTime;
  };
  
public:
 
static CTimerOneManager* GetTimer();  // アドレスゲット関数 兼シングルトン生成 //
  ~CTimerOneManager();
  /* setter */
  void SetTimer(MTimerObserver & aObserver,long aDelay);
  void RemoveTimer(MTimerObserver & aObserver);

  // Arduino TimerCallBask呼び出し関数 /
  void TimerOneFire();
private:
  CTimerOneManager();
  
private:
  /* コールバック呼び出し用アドレス */
 
static CTimerOneManager* gTimerPointer;
  
  /* 10個までタイマーの応答を登録できる */
  TObserverStr Array[CArrayNum];
  int iSetClassNum;
  long iTimerCounter;
};

 

なんと!

コンストラクタがプライベートになっているのが衝撃的ですよね。

 

処理の中身や考え方は、

 

 

 

この時のC言語と一緒なので割愛します。

 

 

あとは、

X秒後にコールバックをしてほしい、Mクラスを継承したクラスから、

以下の関数を呼び出すだけで、

  (CTimerOneManager::GetTimer())->SetTimer(*this,delay);

 

delay秒後にOnTimerFire()が呼び出されるようになります。


また、

OnTimerFire()関数の中で、再度SetTimer(*this,delay)を呼び出せば、

定期呼び出しとしても使えるようになりますね!

 

 

それではクラスを実装してみましょう。

 

コードは最後。

 

 

 

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

雑談

SAIがシングルトンオブジェクトを知った2004年~2005年?ころは、

インターネットでシングルトンと打ち込んでも、

オブジェクト指向言語の関連のサイトは見当たりませんでした。

 

出てくるのは、独身という翻訳サイトばっかり。

 

今、15年以上ぶりに検索したら、オブジェクト指向系のサイトが山のように出て気ました。

 

いい時代になったものです。

 

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

 

◆ヘッダ(Mクラス)

/*
 * @file    MTimerObserver
 * @brief   Lesson A_5 タイマーを制御するクラスを作成してみよう。
 * @author  SAI
 * @date    2021/04/04
 * @note    タイマーの純粋仮想コールバック関数を宣言する為のコールバック用基底クラス
 */
class MTimerObserver
{
public:
  // コールバック用の純粋仮想関数 /
  virtual void OnTimerFire() = 0;
};
 

◆ヘッダ(タイマクラス)

/* クラス定義 */
class MTimerObserver;

const int CArrayNum = 10;

/*
 * @file    CTimerOneManager
 * @brief   Lesson A_5 タイマーマネージメントクラス。
 * @author  SAI
 * @date    2021/04/04
 * @note    タイマー登録してコールバック制御するマネージメントクラス
 */
class CTimerOneManager
{
public:
  /* タイマー登録配列用クラス */
  class TObserverStr
  {
  public:
    MTimerObserver* iTimerObserver;
    long iFierTime;
  };
  
public:
  static CTimerOneManager* GetTimer();
  ~CTimerOneManager();
  /* setter */
  void SetTimer(MTimerObserver & aObserver,long aDelay);
  void RemoveTimer(MTimerObserver & aObserver);

  // Arduino TimerCallBask呼び出し関数 /
  void TimerOneFire();
private:
  CTimerOneManager();
  
private:
  /* コールバック呼び出し用アドレス */
  static CTimerOneManager* gTimerPointer;
  
  /* 10個までタイマーの応答を登録できる */
  TObserverStr Array[CArrayNum];
  int iSetClassNum;
  long iTimerCounter;
};
 

◆ソースコード(タイマクラス)

CTimerOneManager.cpp

/*
 * @file    CTimerOneManager
 * @brief   Lesson A_5 タイマーマネージメントクラス。
 * @author  SAI
 * @date    2021/04/04
 * @note    タイマー登録してコールバック制御するマネージメントクラス
 */

/* *** Include *** */
#include <TimerOne.h>
/* 注意
   TimerOne.h を使用するためには、
   スケッチ -> ライブラリのインクルード -> ライブラリの管理
   から、TimerOne をインストールしておく必要があります。
*/
#include <Arduino.h>

#ifndef __MTimerObserver_
#include "MTimerObserver.h"
#endif

#ifndef __CTimerOneManager__
#include "CTimerOneManager.h"
#endif


/* オーバーフロー防止用のクリア時間 us */
const long COverFlowTime = 1000*1000*1000;

const CTimerOneManager::TObserverStr TObserverStrInit = {nullptr , 0};
//タイマーの応答時間をマイクロ秒単位で設定 /
const int CTIMER_DELAY = 1000;

/* コールバック呼び出し用アドレス */
static CTimerOneManager* CTimerOneManager::gTimerPointer = nullptr;

/* TimerOneに呼び出してもらう関数 */
static void TimerOneFire()
{
  if(CTimerOneManager::GetTimer() != nullptr)
  {
    CTimerOneManager::GetTimer()->TimerOneFire();
  }
}


/* シングルトンオブジェクト取得 */
static CTimerOneManager* CTimerOneManager::GetTimer()
{
  if(CTimerOneManager::gTimerPointer ==nullptr)
  {
    /* なければ生成 */
    CTimerOneManager::gTimerPointer = new CTimerOneManager();
  }
  return CTimerOneManager::gTimerPointer;
}
  

 /*
 * @fn    初期設定
 * @brief コンストラクタ
 * @param -
 */
CTimerOneManager::CTimerOneManager()
:iSetClassNum(0),iTimerCounter(0)
{
  gTimerPointer = this;
  for(int i=0; i<CArrayNum;i++)
  {
    Array[i] = TObserverStrInit;
  }

  /* タイマー用のカウンターを初期化 */
  // Timer setting  タイマー設定
  Timer1.initialize(CTIMER_DELAY); //マイクロ秒単位で設定 /
  Timer1.attachInterrupt(::TimerOneFire); // コールバック関数設定 /
  
}

/*
 * @fn    終了設定
 * @brief デストラクタ
 * @note  終了時には必ずリソースを終了状態にする。
 *        生成したヒープ、つないだセッションなど開放する
 */
CTimerOneManager::~CTimerOneManager()
{
  
}

void CTimerOneManager::SetTimer(MTimerObserver & aObserver,long aDelay)
{
  Array[iSetClassNum].iTimerObserver  = &aObserver;
  Array[iSetClassNum].iFierTime       = iTimerCounter + aDelay;
  // over flow 対策 /
  if(COverFlowTime > Array[iSetClassNum].iFierTime)
  {
    Array[iSetClassNum].iFierTime -= COverFlowTime;
  }
  // 保存数を更新 /
  iSetClassNum++;
}

void CTimerOneManager::RemoveTimer(MTimerObserver & aObserver)
{
  int lClassNum = 0;
  for( ; lClassNum < iSetClassNum ; lClassNum++)
  {
    if(Array[lClassNum].iTimerObserver == &aObserver)
    {
      break;
    }
  }

  /* 見つかったアドレス以降はスライドする */
  for( ; lClassNum < iSetClassNum-1 ; lClassNum++)
  {
    Array[lClassNum].iTimerObserver = Array[lClassNum+1].iTimerObserver;
    Array[lClassNum].iFierTime      = Array[lClassNum+1].iFierTime;
  }
  Array[iSetClassNum].iTimerObserver = nullptr;
  Array[iSetClassNum].iFierTime      = 0;
  
  // 保存数を更新 /
  iSetClassNum--;
}
  

// Arduino TimerCallBask呼び出し関数 /
void CTimerOneManager::TimerOneFire()
{
  iTimerCounter++;
  long lTime = (iTimerCounter*CTIMER_DELAY);
  
  for( int lClassNum = 0 ; lClassNum < iSetClassNum ; lClassNum++)
  {
    /* 発動時間を超えていたら、コールバック実施 */
    if(Array[lClassNum].iFierTime < lTime)
    {
     Array[lClassNum].iTimerObserver->OnTimerFire();
     RemoveTimer(*(Array[lClassNum].iTimerObserver));
    }
  }

};
  

使い方

コールバックしてほしいクラスで、MTimerObserverクラスを継承する。

  ⇒ class CSegControlClass : public MTimerObserver

コールバックしてほしいクラスに、  void OnTimerFire()override;を実装する。

  

だけですね。