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

SAIです。

 

さて、前回はC言語で関数ポインター呼び出しを勉強しました。

 

 


これを使いこなせたら便利ですよね?

 

じゃ、これをC++言語でもやってみましょうか!

 

改造のもとにするコードは、↓この日記のソースにしましょう。

 

 

 

A-4は7セグを1秒ごとに数字をカウントするプログラムですね。

 

1秒ごとの呼び出しを改造したいので、改造すべき処理を抜粋すると、

以下のコードが1秒ごとに呼び出していましたね。

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

  CSegControlClass  lSegCtrlClass(C_7SegSetter);
 
  /* 3つを同時に点灯 */
  for(int i=0 ; i < 100;i++)
  {
    lSegCtrlClass.SetOutput(i);      //  1秒ごとにカウントアップ //
    delay(1000);
  }

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

 

これを関数ポインタに変更すればいいわけです。(あっさり)

 

C言語の方法を流用して記載するとこんな感じになりますね。

 

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

/* 定数定義 */

// 関数配列 //
typedef void (*pFunc)(int aNum);

pFunc FunctionList[] =
{
  &CSegControlClass::SetOutput   /* 機能1 7Seg表示関数 */
};

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

~中略~

  /* 3つを同時に点灯 */

  for(int i=0 ; i < 100;i++)
  {

    (void)(FunctionList[0])(i);      //  1秒ごとにカウントアップ //
    delay(1000);
  }

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

 

 

・・・なのですが、

 

じつはこれ、ビルドエラーになります。

  cannot convert 'void (CSegControlClass::*)(int)' to 'pFunc {aka void (*)(int)}' in initialization  

 

言っている意味が分かりにくいですよね。

 

パッと見は”型があっていません”といっているように見えますが、

実際のところは、全然違うところにあるんです。

 

関数ポインタ呼び出しですが、

文字通り、関数のポインタから呼び出すというものです。

 

 

今回C言語で呼び出ししようとしている関数ですが、

クラスのメンバー関数です。

 

以前少し触れましたが、

クラスってC言語でいうと、構造体のようなもので、型を定義している状態であり、

関数のポインタが存在していない状態なのです。

(厳密には、関数はROM領域に配置されるんですが、直接実行できるものではないんです。とある情報が足りない。

 

思い出してください。

構造体のポインタを示すためには、該当の構造体の実体が存在する必要がありますよね?

ROMに配置されて、どこのデータがその変数なのかわからないと、

アドレスにしたくても無理です。

 

また、実体にするにしても、構造体は定義を変えれば沢山作成できますよね。

同様に、クラスもたくさん作れるのです。

 

CSegControlClassの実体を複数個作成した場合、

どのクラスのどの変数や関数が、呼び出したいデータ(アドレス)になるのかわかりませんよね?

 

というわけで、エラーになるわけです。

 


なんとなく理解できたでしょうか?

 

 

実は今回のような使い方の場合、

関数ポインタというのは、C++言語の思想から少し外れているのです。

 

ただ単に関数を呼び出す程度であれば、C++言語には素晴らしい使い方が用意されています。

 

ちょっと長くなるので、本日はここでいったん区切ります。

 

次はC++言語でのコールバックの作り方をC++言語学びます。

 

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

雑談

 

C++言語でも、状態遷移を作る際には、関数ポインタは大活躍します。

したがって、C++言語の場合、関数ポインタがだめというわけではないのです。

 

 

実は、この内容。

2000年代前半のころ・・・SAIが駆け出し2年の新人の頃です。

 

携帯電話のOSが、μITronからSymbianOSに代わる際の話です。

 

お客様から、とある状態遷移の処理をC++言語で、

関数ポインタを使ってメンテナンス性の良い美しいもの(コード)を実装しろと命令されて、

当時非常に悩みました。

 

メンテナンス性を考慮したら、const配列で定義する必要があるのですが、

そうすると、ビルド時点でアドレス確定しておく必要があります。

 

が、上記の通り、クラス内にメンバー関数を配置すると、

アドレスが決定していないので、ビルドが通るわけないのです。

 

C++言語で関数ポインタを使う = アドレスが確定している必要がある。

つまり、クラス生成後でないといけない。


 

これをどうやったらクリアできるのか?

SAIは、1日中悩みました。

(当時、セキュリティエリアでインターネット禁止でしたし、

 そもそも当時はインターネットにその手の情報があまりありませんでした。)

 

そんな中、若造が悩んだ挙句に出た結論が、

ビルド時点で配置するには、staticとして関数を定義するしかない。

 

が、問題としては、C++言語の思想とはかけ離れているのですよね。

 

なぜかというと、Static関数からは、メンバ変数にアクセスできません。

ということは、クラス内の全関数と全変数がStaticになってしまいます。

 

こうなってくると、クラスにする意味が全くないのです。

 

お客さんの要望である、美しいものという内容に対して、

C++言語の思想としてはかけ離れるのです。

 

ただ、当時、それもC++言語があまり普及していない時代です。

駆け出し2年の若造が、ベースクラスをあまり使いこなしているわけもなく、

 

SAIは一人悩んだ挙句、1週間かけて3000ステップ程度の処理を作り上げました。

(ちなみに、先輩方はC言語の経験はベテランでしたが、

C++言語に関してはSAIに聞くレベルなので、質問しても期待できずです。)

 

 

1週間後、中間報告でお客さんに見せた瞬間、

static関数まみれでダメじゃないかとおしかりを受けました。

 

その後、お客さんがベースクラスについての情報を教えてくださったおかげで、

1週間かけて作り直すことができました。

 

 

というか駆け出し2年目にやらす作業じゃないと、今になって思いました。


 

 

後から知ったのですが、お客さんはSAIは若く見えるけど、そこそこベテランさんだと

勘違いされていたそうです。

 

この出来事は、良き?思い出になりました。

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