#この記事について
C言語のマクロのみを用いて、C++のSTLライクな方法で型非依存な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言語におけるジェネリックプログラミングの一つかと思いますが、マクロを多用したプログラムでエラーが発生した場合、デバッグがやりにくくなるケースがあるため、実際の開発現場での利用においては注意が必要です。