組込み系プログラマの終末

プログラミング、デバイスドライバ、資格、試験、等などなど

ラッパー関数とは?メリットと使いドコロ

ラッパー関数、ラップ関数、または単にラッパー等、色々な呼ばれますが、ここでは「ラッパー関数」で統一します。

ラッパー関数のラッパーはwrap【包む】から来ています。もともとある関数を包む関数、それがラッパー関数です。

今回はラッパー関数の使いドコロをまとめたいと思います。

最初に包まれる関数たちを紹介します。サンプルコードはC/C++で記述します。

//nano単位でsleepする
int nanosleep(const struct timespec *req, struct timespec *rem); 
//時刻time_tを渡すと時刻を表す文字列を返す 
char *ctime_r(const time_t *timep, char *buf);

ラッパー関数の役割①呼び出しの簡略化

nanosleepの簡略化

nanosleepの呼び出しにはtimespec構造体が必要です。引数に構造体を指定する関数はそれだけで使いにくくなりがちです。さらに個人的にはナノ単位よりも秒単位の方が好きですし一般的なので秒表示にします。以下のように「引数を簡略化」「単位を変更」したラッパー関数を作りました。

int my_sleep(double sec)
{
    double  integer;
    double  fraction;
    fraction = modf(sec, &integer);
    struct timespec req={(int)integer,(int)(fraction*1000*1000*1000)};
    return nanosleep(&req, NULL);
}
    //呼び出し元
    //my_sleep(1.5);

ctime_rの簡略化

ctime_rとはctimeのスレッドセーフ版です。スレッドセーフにするために作業用データ領域を第二引数で渡しています。引数が増えるとそれだけで呼び出しをためらってしまいます・・・。またctime_rは戻り値で文字列を返してきますが、このような関数は文字列メモリをライブラリ内で管理しているため戻り値をいつまでも保持しておくことはできません。戻り値は実行したその行でのみしか正しいと保証されていません。以下のように「引数を簡略化」「文字列メモリの管理をライブラリから呼び出し元に変更」したラッパー関数を作りました。

void get_time_str(char *p_data,int size)
{
    time_t timer;
    time(&timer);
    char buf[256];
    strncpy(p_data,ctime_r(&timer,buf),size);
}

    //呼び出し元
    //char aaa[256];
    //get_time_str(aaa,256);
    //printf("%s\n", aaa);

ラッパー関数の役割②異常処理の統一

nanosleepの異常処理

nanosleepの戻り値は関数実行の成否を示しています。仕様は以下です。

呼び出しがシグナルハンドラーにより割り込まれた場合、 nanosleep は -1 を返し、 errno に EINTR を設定

例えばC++でnanosleepを使用する場合を想定しますと、C++は異常を通知する仕組みとして例外があります。C++で普通に書くならば(コーディング規約で例外禁止でないなら)異常は適切に例外を送出するべきです。戻り値での異常通知と例外での異常通知を混在させると、特に戻り値での異常通知が無視されてしまいがちです。

  //引数を見ていないため、失敗していても気付けない!
  nanosleep(timespec,null);

このような事態を避けるために「異常を例外化」したラッパーを作りました。この変更によって戻り値で戻すものがなくなったため、void型に変更しています。

void my_sleep(double sec)
{
    double  integer;
    double  fraction;
    char buf[256];
    fraction = modf(sec, &integer);
    struct timespec req={(int)integer,(int)(fraction*1000*1000*1000)};
    if(nanosleep(&req, NULL) == -1)
    {
         strncpy(buf,strerror(errno),256);
         throw std::runtime_error(buf);
     }
}
    //呼び出し元
    //my_sleep(1.5);

ラッパー関数の役割③プラットフォームの違いを吸収する

実はスリープする関数はWindowsLinuxで関数名が違います。my_sleepはlinuxのライブラリを使用しているため、このままではWindows(VCやVC++)ではコンパイルできません。まぁ殆どの場合それで困らないのですが、環境マクロを使うことで実行するプログラムを分けることができ、ラッパー関数は環境マクロで分類するのに適したレイヤーです。

Windows側のコードは未検証です。

int my_sleep(double sec)
{
#ifdef _WIN32
    //Windows
    int ms=(int)(sec*1000);
    Sleep(ms);
#else
    //Linux
    double  integer;
    double  fraction;
    char buf[256];
    fraction = modf(sec, &integer);
    struct timespec req={(int)integer,(int)(fraction*1000*1000*1000)};
    if(nanosleep(&req, NULL) == -1)
     {
         strncpy(buf,strerror(errno),256);
          throw std::runtime_error(buf);
     }
#endif
}

ラッパー関数の役割④機能の付加

どちらかと言えばラッパー関数を作る際は引数を隠蔽するなど機能を削ることが多い気がしますが、逆に機能を付加することもできます。例えばprintfに何か文字を追加するなどが考えられます。以下は単純に「debug!!」を付加しているだけすが、ファイル名、行番号、時間等を付加することで汎用的なログ化することもできます。ファイルディスクリプタをstdoutにするとprintfの用に振るまい、ファイルポインタを指定するとファイルに出力できます。

void debug_printf(FILE *fp, const char* format, ...){
  va_list arg;
  va_start(arg, format);
  char buf[1024];
  sprintf(buf,"debug!! %s",format);
  vfprintf(fp, buf, arg); 
  va_end(arg);
}
 //debug_printf(stdout,"aaa %d",10);

ラッパー関数の役割⑤名前の変更

分かりにくい名前のライブラリ関数の名前を変えます。上で既にあげていますが、ctime関数のままだと多少分かりにくいのでget_time_strに変更しています。

void get_time_str(char *p_data,int size)
{
    time_t timer;
    time(&timer);
    char buf[256];
    strncpy(p_data,ctime_r(&timer,buf),size);
}

まとめ

ラッパー関数はライブラリの機能に手を加えずに変更をかけられるため便利なテクニックです。ただし実行時はオーバーヘッドになるため、速度的に若干不利になります。またラッパー関数をまたラッパーして、またラッパーして・・・と複数ネストしてしまうと逆にわかりにくくなってしまうため、ラップする目的を決めて設計することが大事ですね。