LoginSignup
0
2

More than 3 years have passed since last update.

C言語でジェネリックプログラミングによる型非依存なforeachアルゴリズムを実装する

Last updated at Posted at 2020-11-09

この記事について

C言語のマクロのみを用いて、C++のSTLライクな方法で型非依存なforeachを実現する実装例を記載します。

概要

  • マクロや関数を用いてforeachで走査したいコンテナに対応した開始位置、終了位置、順方向イテレータを返却する処理を定義します
  • foreachのアルゴリズムをこれらの処理を使ってマクロで記述します

実装例

foreachのマクロ定義を記載します。このマクロは以下の三つの処理を要求します。

  • 走査するコンテナの開始位置のポインタを返却する処理
  • 走査するコンテナの終了位置のポインタを返却する処理
  • 次に走査するアイテムのポインタを返却する処理
foreachの実装マクロ
/**
 * @def     foreach
 * @brief   コンテナ逐次走査用foreachマクロ
 *
 * @note    Type型のコンテナに対して以下の関数,マクロが実装されていることが前提
 *  -   TypeIterator* Type_Begin( &Type )
 *  -   TypeIterator* Type_End( &Type )
 *  -   TypeIterator* Type_Iterator_Next( &Type, &Type_Iterator )
 *
 * @param   Type        走査対象のコンテナの型
 * @param   pItr        走査対象のコンテナに対する宣言済みの順方向イテレータ変数名
 * @param   Container   走査対象の宣言済みのコンテナ変数名
 */
#define foreach( Type, pItr, Container )                     \
    for( (pItr)  = Type##_Begin( Container );                \
         (pItr) != Type##_End( Container );                  \
         (pItr)  = Type##_Iterator_Next( Container, pItr) )  \

配列に適用した場合

固定配列に適用するため、foreachマクロが要求する三つの処理を定義します。

配列の走査用定義
#define Array_Begin( Container )                  ( &(Container[0]) )
#define Array_End( Container )                    ( &(Container[ARRAY_GET_LENGTH(Container)]) )
#define Array_Iterator_Next( Container, pItr )    ( (pItr) + 1 )

/* 配列長を算出する補助マクロ */
#define ARRAY_GET_LENGTH(array)                        (sizeof(array)/sizeof(array[0]))

使用例は以下のようになります。

// ループ内の何らかの処理
void func(void* p){
    printf("%p\n", p);
}

// 使用例
void main(void){
    int  items[4] = {0};    
    int* p_itr;

    foreach( Array, p_itr, items){
        // p_itrに&items[0]~&items[3]までのポインタが順次格納されます
        func(p_itr);
    }
}

コンテナの型を変換した場合

foreachマクロが要求する三つの処理を定義できれば、foreachのループ処理を変更することなくコンテナの種別を変更できます。

例えば、単純な線形リストと、そのコンテナ型に対する開始、終了、順方向イテレータの処理を定義してforeachを利用してみます。

線形リストの走査用定義
typedef struct list{
    struct list* pNext;
}List;


List* List_Begin(List* p_this){
    return p_this;
}

List* List_End(List* p_this){
    return NULL;
}

List* List_Iterator_Next(List* p_this, List* p_itr){
    return p_itr->pNext;
}

使用例は以下の通りです。

// リスト初期化
void List_Init(List* p_this){
    p_this->pNext = NULL;
}

// リストへアイテムを挿入
void List_Insert(List* p_this, List* p_item){
    List* p_itr = p_this;
    while( p_itr->pNext != NULL){
        p_itr = p_itr->pNext;
    }
    p_itr->pNext  = p_item;
    p_item->pNext = NULL;
}

// ループ内の何らかの処理
void func(void* p){
    printf("%p\n", p);
}

// 使用例
void main(void){
    List  list;
    List  item1, item2, item3;    
    List* p_itr;

    // リストを初期化
    List_Init(&list);
    List_Insert(&list, &item1);
    List_Insert(&list, &item2);
    List_Insert(&list, &item3);

    // 引数の型指定をListに変更しています
    // 走査処理自体は固定配列の場合と同じ記述が利用できます
    foreach( List, p_itr, &list){
        func(p_itr);
    }
}

開始位置のポインタの返却、終了位置のポインタの返却、順方向走査イテレーション処理とそのポインタの返却の三つの処理が定義できるコンテナであれば、任意の型に対してforeachマクロを使用することができます。

この方法はC++のSTLで用いられているアルゴリズムとコンテナ型を分離する考え方を参考にしています。

コンテナの順次走査というforeach以外においても、同様の考え方でマクロを使ってアルゴリズムを組むことで、C言語においてもコンテナ非依存のアルゴリズムを作成することができると思います。

この方法はC言語におけるジェネリックプログラミングの一つかと思いますが、マクロを多用したプログラムでエラーが発生した場合、デバッグがやりにくくなるケースがあるため、実際の開発現場での利用においては注意が必要です。

0
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
2