2012-02-22 6 views
5

Cでマクロを使用して、タイプセーフな汎用リンクリストを作成しようとしています。 C++でのテンプレートの動作と同様に動作するはずです。例えば、タイプセーフなマクロを持つ汎用コンテナ

LIST(int) *list = LIST_CREATE(int); 

私の最初の試みは#define LIST(TYPE)ためstruct _List_##TYPE {...}を定義する(Iは、上記の使用マクロ)でした。しかし、新しいリストを宣言するたびに構造体が再定義されるため、これはうまくいきませんでした。私はこのことで問題を解消:

/* You would first have to use this macro, which will define 
    the `struct _List_##TYPE`...        */ 
DEFINE_LIST(int); 

int main(void) 
{ 
    /* ... And this macro would just be an alias for the struct, it 
     wouldn't actually define it.         */ 
    LIST(int) *list = LIST_CREATE(int); 
    return 0; 
} 

/* This is how the macros look like */ 

#define DEFINE_LIST(TYPE) \ 
    struct _List_##TYPE  \ 
    {      \ 
     ...     \ 
    } 

#define LIST(TYPE)  \ 
    struct _List_##TYPE 

をしかし、別の問題は、私は、たとえば、DEFINE_LIST(int)を使用する複数のファイルを持っており、それらのいくつかはお互いを含む場合、その後、まだ同じの複数の定義があるだろうということです構造体。 DEFINE_LIST構造体が既に定義されているかどうかを確認する方法はありますか?

+0

その構文はクールです:) –

+0

[OpenGC3](https://github.com/kevin-dong-nai-jia/OpenGC3)はあなたが探しているものです。 ['ccxll(T)'](https://github.com/kevin-dong-nai-jia/OpenGC3/blob/master/doc/ccxll-list.pdf)でも[入れ子にする]ことができます(https:// gist.github.com/kevin-dong-nai-jia/af150182091f2871a92176b15965f814)! –

答えて

0

なぜライブラリを使用しませんか? 私はGLibのを使用したいが、私はいくつかの非常に簡単なマクロのコード化はGLibが提供するデータ型の型保証されたバージョンを取得するためには、私のコード内にボイドポインタを憎む:私はしたい場合

http://pastebin.com/Pc0KsadV

を記号*のリスト(それは私が先に定義されたタイプだと仮定して)私はちょうどに行う必要があります。

GLIST_INSTANCE(SymbolList, Symbol*, symbol_list); 

リンクされたシンプルなため(やり過ぎのようなものだろう)全体のライブラリを使用しない場合void *を処理するリストを実装し、正しい型キャストをカプセル化して作成する関数をいくつか作成します。

+2

タイプセーフであるかどうかはわかりません。 –

+0

これらのマクロはタイプセーフではありません。異なるリストごとに生成される関数は、他のリスト型を受け入れます。 – ugoren

+0

@ugoren、trueですが、異なる引数型を受け入れません。たとえば、 'symbol_list_append(OtherTypeList、otherTypeObj);を呼び出すと失敗します。 – Victor

0

list_template.hファイルを作成し、テンプレートのすべてのインスタンスに対してlist_TYPE.hファイルとlist_TYPE.cファイルを作成する方法はありますか。もちろん、これらには適切なヘッダープロテクターが付属しています。 テンプレートヘッダーのみを含めることができますが、すべての.cファイルをコンパイルおよびリンクプロセスに追加してください。

これは、インスタンスを複製... C++があなたのために自動的に何をするか、基本的には...

1

あなたは常に「名前」リストにあなたをできるようになりますDEFINE_LISTマクロに第二引数を追加することができます。例えば:

#define DEFINE_LIST(TYPE, NAME)   \ 
struct _List_##TYPE_##NAME    \ 
{          \ 
    TYPE member_1;      \ 
    struct _List_##TYPE_##NAME* next; \ 
} 

は、その後、あなたは、単に行うことができます:

DEFINE_LIST(int, my_list); 
//... more code that uses the "my_list" type 

あなたはちょうど2つの異なるヘッダファイルが互いを含む場合、同じリスト「name」を再使用していないために自分自身を制限しなければなりません、両方ともDEFINE_LISTマクロを使用します。 LIST_CREATEなどを使用する場合は、リストを名前で参照する必要があります。

リストを作成した関数に渡すときには、ユーザー定義の「名前付き」バージョンにキャストされています。 structの実際の情報は同じであり、 "名前"タグはバイナリの観点ではなく宣言から区別するだけなので、何も影響しません。例えば、ここにint型を格納リストオブジェクトをとる関数は次のとおりです。

#define GENERIC_LIST_PTR(TYPE) struct _generic_list_type_##TYPE* 
#define LIST_CAST_PTR(OBJ, TYPE) (GENERIC_LIST_PTR(TYPE))(OBJ) 

void function(GENERIC_LIST_PTR(INT) list) 
{ 
    //...use list as normal (i.e., access it's int data-member, etc.) 
} 

DEFINE_LIST(int, my_list); 

int main() 
{ 
    LIST(int, my_list)* list = LIST_CREATE(int, my_list); 
    function(LIST_CAST_PTR(list, int)); 

    //...more code 

    return 0; 
} 

私は、これは必ずしも最も便利なものではありません知っているが、これは命名の問題を解決し、あなたは何のバージョンのを制御することができますstruct _generic_list_type_XXXは、他のユーザーが追加しないプライベートヘッダーファイルに作成されます(独自の型にしたい場合を除きます)...しかし、ジェネリックリストの宣言と定義を分離する仕組みになります実際のユーザ定義リスト型からの型。

+0

しかし、特定のタイプのリストを受け入れる関数を書くにはどうすればいいですか?彼らがリストの「名前」を知っていることを期待しても、あまりにも良いとは思わない。 –

+0

「名前付き」のバージョンは重要な部分ではないため、常にキャストを使用できます。私は私の答えに例を追加します。 – Jason

0

私は実際に存在をチェックして(構造体)を1つのマクロで定義することはできますか? DEFINE_LIST(int)の前にもう一度#ifndefのチェックを入れてください。エレガントではありませんが、あなたが望むことをします。

8

私はC++がテンプレートを取得する前にこの問題に取り組みましたが、まだ にコードがあります。

完全にヘッダーファイルに限定された形で、 というマクロを持つ本当のジェネリックなtypesafeコンテナーオブテンプレートを定義することはできません。標準プリプロセッサ は、 が必要とするマクロ割り当てを「プッシュ」および「ポップ」する手段を提供しないため、ネストされたシーケンシャルな の展開のコンテキストで整合性を保持します。 Tのコンテナを定義して自分のドッグフードを食べようとするとすぐにネストされたコンテキストに遭遇します。

私たちが見るように、できることはできますが、@immortalが示唆するように、必要なTの値ごとに異なる.h.cファイルを生成します。 あなたは、例えば、list_type.inl、たとえば、 にマクロをインラインファイルを完全にジェネリックリストの-Tを定義し、次に 小さなセットアップラッパーのペアのそれぞれにおけるlist_type.inl含めることができます - list_float.hlist_float.c - それを はそれぞれ、浮動小数点リストコンテナを定義して実装します。同様に のint、list-of-floatのリスト、double-of-listのリスト、 などです。

模範的な例は、すべて明らかになります。しかし、まずは の食べ物を自分で食べてください。

このような2次コンテナをリストのリストとして考えてみましょう。 list-of-TソリューションのT = list-of-thingummyを設定することで、これらをインスタンス化することができます。しかし、決してlist-of-thingummyはPOD データ型になることはありません。リストの物が自分のドッグフードであろうと他のものであろうと、 はヒープ上に存在する抽象データ型になり、 のtypedef-edポインタ型で表されます。または少なくとも、 は動的コンポーネントをヒープ上に保持することになります。いずれにしても、PODではありません。

これは、私たちのlist-of-Tソリューションが、 T = list-of-thingummyということだけでは不十分だということです。また、Tが非POD のコピー構築と破壊を必要としているかどうか、もしそうならコピーを構築して破棄する方法を とする必要があります。 Cの用語では、それは意味:

  • コピー建設:そのような領域のアドレス与えられ、コミットされていないメモリのT-サイズ 地域に与えられたTのコピーを作成する方法。

  • 破壊:指定したアドレスにTを破壊する方法。我々は合理的 にユーザー提供のオリジナルからコピーされたオブジェクトの封じ込めを私たちのリスト-の-Tソリューションを制限することができますよう私たち、 非Tパラメータからデフォルトの建設や建設について知らなくても行うことができます

。しかし、私たちは をコピーしなければなりません。コピーを処分する必要があります。

次に、Tのリストに加えて、T1-to-T2のマップ、または のテンプレートを提供することを目指しているとします。これらのキー順のデータ型は、我々は、キータイプの 任意の2つのオブジェクトを注文すること、すなわちどのように、TまたはT1のいずれかの非PODの値のためにプラグインする必要があります別のパラメータ を追加します。実際、我々は memcmp()はしないだろうそのために、任意のキーのデータ型のためにそのパラメータが必要になります。

は、我々が の概略例えば単純なリストの-Tの問題に固執するだろう、と述べました。さらに簡潔にするために、私は const APIの望ましさ を忘れてしまいます。

これと他のテンプレートコンテナタイプの場合は、 とおそらく他のユーティリティマクロに加えて、便利なように関数とタイプの識別子を組み立てるためのトークン貼り付け用のマクロが必要です。これらはすべて、ヘッダに行くと言うことができmacro_kit.h、 例えば:list_type.inlの概略構造次に

#ifndef MACRO_KIT_H 
#define MACRO_KIT_H 

/* macro_kit.h */ 

#define _CAT2(x,y) x##y 

// Concatenate 2 tokens x and y 
#define CAT2(x,y) _CAT2(x,y) 
// Concatenate 3 tokens x, y and z 
#define CAT3(x,y,z) CAT2(x,CAT2(y,z)) 

// Join 2 tokens x and y with '_' = x_y 
#define JOIN2(x,y) CAT3(x,_,y) 
// Join 3 tokens x, y and z with '_' = x_y_z 
#define JOIN3(x,y,z) JOIN2(x,JOIN2(y,z)) 
// Compute the memory footprint of n T's 
#define SPAN(n,T) ((n) * sizeof(T)) 

#endif 

list_type.inlはmutliple包含に対してはマクロガードを有していない
//! There is intentionally no idempotence guard on this file 
#include "macro_kit.h" 
#include <stddef.h> 

#ifndef INCLUDE_LIST_TYPE_INL 
#error This file should only be included from headers \ 
that define INCLUDE_LIST_TYPE_INL 
#endif 

#ifndef LIST_ELEMENT_TYPE 
#error Need a definition for LIST_ELEMENT_TYPE 
#endif 

/* list_type.inl 

    Defines and implements a generic list-of-T container 
    for T the current values of the macros: 

    - LIST_ELEMENT_TYPE: 
     - must have a definition = the datatype (or typedef alias) for \ 
     which a list container is required. 

    - LIST_ELEMENT_COPY_INITOR: 
     - If undefined, then LIST_ELEMENT_TYPE is assumed to be copy- 
     initializable by the assignment operator. Otherwise must be defined 
     as the name of a copy initialization function having a prototype of 
     the form: 

     LIST_ELEMENT_TYPE * copy_initor_name(LIST_ELEMENT_TYPE *pdest, 
              LIST_ELEMENT_TYPE *psrc); 

     that will attempt to copy the LIST_ELEMENT_TYPE at `psrc` into the 
     uncommitted memory at `pdest`, returning `pdest` on success and NULL 
     on failure. 

     N.B. This file itself defines the copy initializor for the list-type 
     that it generates. 

    - LIST_ELEMENT_DISPOSE 
     If undefined, then LIST_ELEMENT_TYPE is assumed to need no 
     destruction. Otherwise the name of a destructor function having a 
     protoype of the form: 

     void dtor_name(LIST_ELEMENT_TYPE pt*); 

     that appropriately destroys the LIST_ELEMENT_TYPE at `pt`. 

     N.B. This file itself defines the destructor for the list-type that 
     it generates. 
*/ 

/* Define the names of the list-type to generate, 
    e.g. list_int, list_float 
*/ 
#define LIST_TYPE JOIN2(list,LIST_ELEMENT_TYPE) 

/* Define the function-names of the LIST_TYPE API. 
    Each of the API macros LIST_XXXX generates a function name in 
    which LIST becomes the value of LIST_TYPE and XXXX becomes lowercase, 
    e.g list_int_new 
*/ 
#define LIST_NEW JOIN2(LIST_TYPE,new) 
#define LIST_NODE JOIN2(LIST_TYPE,node) 
#define LIST_DISPOSE JOIN2(LIST_TYPE,dispose) 
#define LIST_COPY_INIT JOIN2(LIST_TYPE,copy_init) 
#define LIST_COPY JOIN2(LIST_TYPE,copy) 
#define LIST_BEGIN JOIN2(LIST_TYPE,begin) 
#define LIST_END JOIN2(LIST_TYPE,end) 
#define LIST_SIZE JOIN2(LIST_TYPE,size) 
#define LIST_INSERT_BEFORE JOIN3(LIST_TYPE,insert,before) 
#define LIST_DELETE_BEFORE JOIN3(LIST_TYPE,delete,before) 
#define LIST_PUSH_BACK JOIN3(LIST_TYPE,push,back) 
#define LIST_PUSH_FRONT JOIN3(LIST_TYPE,push,front) 
#define LIST_POP_BACK JOIN3(LIST_TYPE,pop,back) 
#define LIST_POP_FRONT JOIN3(LIST_TYPE,pop,front) 
#define LIST_NODE_GET JOIN2(LIST_NODE,get) 
#define LIST_NODE_NEXT JOIN2(LIST_NODE,next) 
#define LIST_NODE_PREV JOIN2(LIST_NODE,prev) 

/* Define the name of the structure used to implement a LIST_TYPE. 
    This structure is not exposed to user code. 
*/ 
#define LIST_STRUCT JOIN2(LIST_TYPE,struct) 

/* Define the name of the structure used to implement a node of a LIST_TYPE. 
    This structure is not exposed to user code. 
*/ 
#define LIST_NODE_STRUCT JOIN2(LIST_NODE,struct) 

/* The LIST_TYPE API... */ 


// Define the abstract list type 
typedef struct LIST_STRUCT * LIST_TYPE; 

// Define the abstract list node type 
typedef struct LIST_NODE_STRUCT * LIST_NODE; 

/* Return a pointer to the LIST_ELEMENT_TYPE in a LIST_NODE `node`, 
    or NULL if `node` is null 
*/ 
extern LIST_ELEMENT_TYPE * LIST_NODE_GET(LIST_NODE node); 

/* Return the LIST_NODE successor of a LIST_NODE `node`, 
    or NULL if `node` is null. 
*/ 
extern LIST_NODE LIST_NODE_NEXT(LIST_NODE node); 

/* Return the LIST_NODE predecessor of a LIST_NODE `node`, 
    or NULL if `node` is null. 
*/ 
extern LIST_NODE LIST_NODE_PREV(LIST_NODE node); 


/* Create a new LIST_TYPE optionally initialized with elements copied from 
    `start` and until `end`. 

    If `end` is null it is assumed == `start` + 1. 

    If `start` is not NULL then elements will be appended to the 
    LIST_TYPE until `end` or until an element cannot be successfully copied. 
    The size of the LIST_TYPE will be the number of successfully copied 
    elements. 
*/ 
extern LIST_TYPE LIST_NEW(LIST_ELEMENT_TYPE *start, LIST_ELEMENT_TYPE *end); 

/* Dispose of a LIST_TYPE 
    If the pointer to LIST_TYPE `plist` is not null and addresses 
    a non-null LIST_TYPE then the LIST_TYPE it addresses is 
    destroyed and set NULL. 
*/ 
extern void LIST_DISPOSE(LIST_TYPE * plist); 

/* Copy the LIST_TYPE at `psrc` into the LIST_TYPE-sized region at `pdest`, 
    returning `pdest` on success, else NULL. 

    If copying is unsuccessful the LIST_TYPE-sized region at `pdest is 
    unchanged. 
*/ 
extern LIST_TYPE * LIST_COPY_INIT(LIST_TYPE *pdest, LIST_TYPE *psrc); 

/* Return a copy of the LIST_TYPE `src`, or NULL if `src` cannot be 
    successfully copied. 
*/ 
extern LIST_TYPE LIST_COPY(LIST_TYPE src); 

/* Return a LIST_NODE referring to the start of the 
    LIST_TYPE `list`, or NULL if `list` is null. 
*/ 
extern LIST_NODE LIST_BEGIN(LIST_TYPE list); 

/* Return a LIST_NODE referring to the end of the 
    LIST_TYPE `list`, or NULL if `list` is null. 
*/ 
extern LIST_NODE LIST_END(LIST_TYPE list); 

/* Return the number of LIST_ELEMENT_TYPEs in the LIST_TYPE `list` 
    or 0 if `list` is null. 
*/ 
extern size_t LIST_SIZE(LIST_TYPE list); 

/* Etc. etc. - extern prototypes for all API functions. 
    ... 
    ... 
*/ 

/* If LIST_IMPLEMENT is defined then the implementation of LIST_TYPE is 
    compiled, otherwise skipped. #define LIST_IMPLEMENT to include this 
    file in the .c file that implements LIST_TYPE. Leave it undefined 
    to include this file in the .h file that defines the LIST_TYPE API. 
*/ 
#ifdef LIST_IMPLEMENT 
// Implementation code now included. 

// Standard library #includes...? 

// The heap structure of a list node 
struct LIST_NODE_STRUCT { 
    struct LIST_NODE_STRUCT * _next; 
    struct LIST_NODE_STRUCT * _prev; 
    LIST_ELEMENT_TYPE _data[1]; 
}; 

// The heap structure of a LIST_TYPE 
struct LIST_STRUCT { 
    size_t _size; 
    struct LIST_NODE_STRUCT * _anchor; 
}; 

/* Etc. etc. - implementations for all API functions 
    ... 
    ... 
*/ 

/* Undefine LIST_IMPLEMENT whenever it was defined. 
    Should never fall through. 
*/ 
#undef LIST_IMPLEMENT 

#endif // LIST_IMPLEMENT 

/* Always undefine all the LIST_TYPE parameters. 
    Should never fall through. 
*/ 
#undef LIST_ELEMENT_TYPE 
#undef LIST_ELEMENT_COPY_INITOR 
#undef LIST_ELEMENT_DISPOSE 
/* Also undefine the "I really meant to include this" flag. */ 

#undef INCLUDE_LIST_TYPE_INL 

留意されたいです。それは が見られるたびに含まれるように - 少なくとも、テンプレートAPI - あなたはそれの 少なくともいくつかをしたいです。

ファイルの先頭にあるコメントを読んだ場合、 のコードをどのようにコード化して、list-of-intコンテナタイプをインポートするための折り返しヘッダを推測できますか。インポートするラッピングヘッダをコード化する方法を

#ifndef LIST_INT_H 
#define LIST_INT_H 

/* list_int.h*/ 

#define LIST_ELEMENT_TYPE int 
#define INCLUDE_LIST_TYPE_INL 
#include "list_type.inl" 

#endif 

と同様にリストのリスト-の-INT コンテナタイプ:

#ifndef LIST_LIST_INT_H 
#define LIST_LIST_INT_H 

/* list_list_int.h*/ 

#define LIST_ELEMENT_TYPE list_int 
#define LIST_ELEMENT_COPY_INIT list_int_copy_init 
#define LIST_ELEMENT_DISPOSE list_int_dispose 
#define INCLUDE_LIST_TYPE_INL 
#include "list_type.inl" 

#endif 

あなたのアプリケーションが安全なラッパーを含むことができ、例えばファイルの最後の数行を参照してください。それは彼らと一緒に行うのリスト型 をパラメータ list_type.inl常に#undefsすべてのマクロので、インクルードは、彼らが競合の方法でLIST_ELEMENT_TYPEを定義するという事実にもかかわらず

#include "list_int.h" 
#include "list_list_int.h" 

すぎ注意マクロLIST_IMPLEMENTの使用。 list_type.inl が解析されたときに未定義の場合、テンプレートAPIのみが公開されます。テンプレートの実装は がスキップされます。 LIST_IMPLEMENTが定義されている場合、ファイル全体がコンパイルされます。したがって、 ラッピングヘッダーは、LIST_IMPLEMENTを定義しないことによって、リストタイプの APIのみをインポートします。

逆に、ラッピングソースファイルlist_int.c,list_list_int.cの場合、 はLIST_IMPLEMENTとなります。その後、そこに行うには何もないけど 対応するヘッダが含ま:

/* list_int.c */ 
#define LIST_IMPLEMENT 
#include "list_int.h" 

と:あなたのアプリケーションで今

/* list_list_int.c*/ 
#include "list_int.h" 
#define LIST_IMPLEMENT 
#include "list_list_int.h" 

、何のリストテンプレートのマクロが表示されませんが。あなたのラップ ヘッダ「実際のコード」に出解析:

#include "list_int.h" 
#include "list_list_int.h" 
// etc. 

int main(void) 
{ 
    int idata[10] = {1,2,3,4,5,6,7,8,9,10}; 
    //... 
    list_int lint = list_int_new(idata,idata + 10); 
    //... 
    list_list_int llint = list_list_int_new(&lint,0); 
    //... 
    list_int_dispose(&lint); 
    //... 
    list_list_int_dispose(&llint); 
    //... 
    exit(0); 
} 

を「Cテンプレートライブラリ」を自分で装備するにはこの方法だけハードワーク は、各コンテナタイプのためにあなたを.inlファイルを書くことです(!)それをテストしたいと思って、 非常に、非常に徹底的に。次に、 の既定のリンケージのネイティブデータ型とコンテナの種類の組み合わせごとにオブジェクトファイル とヘッダーを生成し、 のオンデマンドで.h.cラップをjiffyでノックアウトします。

言うまでもなく、C++がテンプレートを発芽すると、言うまでもなく、発汗のための熱狂が このように蒸発しました。しかし、何らかの理由でCが唯一の選択肢であれば、この方法で完全に を実行することができます。

0

マクロを持つ一般的なタイプセーフなコンテナを作成することは可能です。計算理論の観点から、マクロ拡張から生成された言語(コード)は、非決定論的プッシュダウンオートマトンによって認識することができ、それはせいぜい文脈自由文法であることを意味する。前述のステートメントは、コンテナーとその関連イテレーターが含まれているタイプを覚えておく必要があるため、目標を達成することは不可能に見えますが、これは状況依存の文法によってのみ行うことができます。しかし、我々はいくつかのトリックを行うことができます!

成功への鍵は、シンボルテーブルを構築するコンパイルプロセスにあります。コンパイラーが表を照会したときに変数の型が認識され、安全でない型変換が発生しない場合は、型安全とみなされます。したがって、同じレベルのスコープで2つ以上の構造体が宣言されていると構造体名が競合する可能性があるため、すべてstructに特別な名前を付ける必要があります。最も簡単な方法は、現在の行番号を構造体名に追加することです。標準Cでは、ANSI C(C89/C90)以降の定義済みマクロ__LINE__とマクロconcatenation/stringificationがサポートされています。

次に、上記で定義した構造体にいくつかの属性を隠すだけです。実行時に別のリストレコードを作成する場合は、構造体内にポインタを置くことで実際に問題が解決されます。しかし、これでは十分ではありません。実行時に割り当てられるリストレコードの数を格納するために余分な変数が必要な場合があります。これは、リストがプログラマによって明示的に破棄されたときにメモリを解放する方法を理解するのに役立ちます。また、マクロプログラミングで広く使われている__typeof__()拡張機能を利用することもできます。

私はマクロで、建物のタイプセーフな汎用的なコンテナを目指し、ここでこのライブラリがどのように動作するかの短いと簡単な例であるOpenGC3の著者午前:

ccxll(int) list;      // declare a list of type int 
ccxll_init(list);      // initialize the list record 

for (int cnt = 8; cnt-- > 0;)  // 
    ccxll_push_back(list, rand()); // insert "rand()" to the end 

ccxll_sort(list);      // sort with comparator: XLEQ 

CCXLL_INCR_AUTO(pnum, list)   // traverse the list forward: 
    printf("num = %d\n", *pnum);  // access elems through iters 

ccxll_free(list);      // destroy the list after use 

それは非常によく似ていますSTLの構文リストのタイプは、listが宣言されたときに決定されます。リストへの操作時に安全でない型キャストがないので、型の安全性について心配する必要はありません。

関連する問題