2013年9月23日月曜日

C語でC++のclassっぽい関数呼び出し

C言語でC++等のclassっぽい関数呼び出しを試してみたので忘れないうちに書いておきます
以下コードが幾つか続くので、"もっと読む"で展開してください




/* main.c */
#include <stdio.h>
#include "cTest.h"

int main(int argc, char *argv[]) {

    printf("%d\n", cTest.funcTest(5));

    cTest.value1.set(10);
    printf("%d\n", cTest.funcTest(5));

    printf("%d\n", cTest.value1.get());

    return(0);
}

/* cTest.h */
#ifndef __cTest_H
#define __cTest_H

#include <stdint.h>

typedef struct {
    int (*get)(void);
    void (*set)(int val);
} IntGet_IntSet_t;

typedef struct {
    int (*funcTest)(int x);
    IntGet_IntSet_t value1;
} cTest_t;

extern cTest_t            cTest;


#endif

/* cTest.c */
#include "cTest.h"

static int  Value1 = 1;
static int  value1_Get(void)    { return(Value1); }
static void value1_Set(int val)    { Value1 = val; }

static int fFuncTest(int x)
{
    return(x * Value1);
}

cTest_t cTest = {
    .funcTest    = fFuncTest,
    .value1        = { .get = value1_Get, .set = value1_Set, },

};


このテストコードでは3つのファイルが有り
それぞれ main.c / cTest.c / cTest.h です
それぞれの中身は上のようになっています

この機能を実現するために使うのは関数ポインタと構造体で、それぞれの機能については特に難しいことではないためある程度機能把握していることを前提として話を進めます

まずヘッダファイルですが、ここには構造体の宣言と変数1個をexternで外部から使えるようにしてあるだけです
で、この構造体が重要ですが、構造体の中身は変数ではなく、関数ポインタになっています
そしてexternで外部ソースからアクセスできるため、構造体の変数名.関数名() で関数を呼び出せるという仕組みです
これら以外に通常のヘッダファイルと違うのは関数のプロトタイプ宣言が無いことくらいでしょうか
(構造体の中でプロトタイプ宣言している という感じですが)

次にソースファイルの説明ですが、これはほぼ通常のソースファイルそのものです
通常と違う点はソースファイルの最後に変数の宣言と初期化が行われているということです
これはプロトタイプ宣言が無いため、変数初期化の後ろに関数があった場合にエラーや警告が出るのを防ぐ意味があります

最後にメインのソースファイルですが、これはヘッダファイルをインクルードしているだけです
関数を使いたい場合はヘッダファイルでexternした変数名の後ろにピリオドをつけて、構造体でつけた名前を呼び出せば関数として使えます
もちろん引数や戻り値も使えます


それと少し特殊なサンプルとして、IntGet_IntSet_t構造体を作ってみました
これはC#で使われるプロパティのsetとgetと同じです(C#のset/getを詳しく知らないので断言できませんが)
使い方はサンプルを見ればわかると思いますが、変数に直接アクセスせずに値を書き換えるということができます
そのため変数をexternで外部のソースに見えるようにした場合に名前が重複する等を避ける事ができ、比較的操作も直感的にわかると思います
それと、例えばsetがない構造体を使った場合は変数を外部のソースから変更される事故を減らすことができます

このset/getの問題は、構造体で形を指定してしまっているため、変数の形の数だけ構造体が必要になります
このset/get周りは別のヘッダファイルである程度のバリエーションの構造体を予め作ってしまうという解決方法もあります


この方法で関数や変数にアクセスした場合、直接アクセスするよりも時間がかかるという欠点はありますが、時間に余裕があるプログラムでは有用な方法だと思います
(もっとも、そんな環境ならCを使う理由もないですが)

0 件のコメント:

コメントを投稿