6

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

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

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

/* 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構造体が既に定義されているかどうかを確認する方法はありますか?

/* one.h */
DEFINE_LIST(int);

/* two.h */
#include "one.h"
DEFINE_LIST(int); /* Error: already defined in one.h */ 
4

6 に答える 6

11

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

完全にヘッダーファイルに限定された方法で、マクロを使用して真に汎用的なタイプセーフなコンテナオブTテンプレートを定義することはできません。標準のプリプロセッサには、ネストされたシーケンシャルな展開コンテキストを通じて整合性を維持するために必要なマクロ割り当てを「プッシュ」および「ポップ」する手段はありません。また、Tのコンテナのコンテナを定義して独自のドッグフーディングを食べようとするとすぐに、ネストされたコンテキストに遭遇します。

これから説明するように、これは実行できますが、@ immortalが示唆しているように、必要なTの値ごとに個別.hのファイルを生成する必要があります。.cたとえば、インラインファイルでマクロを使用して完全に汎用的なTのリストを定義し、次に、リストをそれぞれ定義および実装する小さなセットアップラッパーのペアのそれぞれにlist_type.inl含めることができます。 -of-floatコンテナ。同様に、list-of-int、list-of-list-of-float、list-of-vector-of-list-of-doubleなども同様です。list_type.inllist_float.hlist_float.c

概略的な例ですべてが明らかになります。ただし、最初に、ドッグフーディングの課題を完全に把握してください。

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

これは、T=list-of-thingummyとだけ言われるだけではList-of-Tソリューションには不十分であることを意味します。また、Tが非PODのコピー構築と破棄を必要とするかどうか、および必要な場合は、コピー構築と破棄を行う方法についても説明する必要があります。C用語では、それは次のことを意味します。

  • コピー構築:コミットされていないメモリのTサイズの領域に、そのような領域のアドレスを指定して、特定のTのコピーを作成する方法。

  • 破壊:特定のアドレスでTを破壊する方法。

Tリストソリューションをユーザー提供のオリジナルからコピーされたオブジェクトの包含に合理的に制限できるため、デフォルトの構築または非Tパラメーターからの構築について知らなくても実行できます。しかし、私たちはそれらをコピーする必要があり、私たちはコピーを処分しなければなりません。

次に、list-of-Tに加えて、set-of-T、またはmap-of-T1-to-T2のテンプレートを提供することを目指しているとします。これらのキー順データ型は、TまたはT1の非POD値、つまりキータイプの任意の2つのオブジェクトの順序付け方法に対してプラグインする必要がある別のパラメーターを追加します。実際、必要としない主要なデータ型には、そのパラメーターが必要になりmemcmp()ます。

それに注意して、回路図の例では、より単純なTリストの問題に固執します。constさらに簡単にするために、 APIの望ましさを忘れます。

これと他のテンプレートコンテナタイプには、関数とタイプの識別子を便利にアセンブルできるトークン貼り付けマクロと、おそらく他のユーティリティマクロが必要です。これらはすべてヘッダーに入れることができます。たとえばmacro_kit.h、次のようになります。

#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ます。

//! 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

list_type.inl複数の包含に対するマクロガードがないことに注意してください。少なくともその一部(少なくともテンプレートAPI)は、表示されるたびに含まれるようにする必要があります。

ファイルの上部にあるコメントを読むと、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

同様に、ラッピングヘッダーをコーディングしてlist-of-list-of-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 

アプリケーションには、このようなラッパーを安全に含めることができます。

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

それが行われるときにリストタイプをパラメータ化するすべてのマクロが常にLIST_ELEMENT_TYPEあるため、それらは矛盾する方法で 定義されているという事実にもかかわらず、ファイルの最後の数行を参照してください。list_type.inl#undefs

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

逆に、ソースファイルをラッピングする場合list_int.clist_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

言うまでもなく、C ++がテンプレートを発芽させるとすぐに、この方法でそれらを発汗させることへの私の熱意は蒸発しました。しかし、何らかの理由でCが唯一のオプションである場合は、この方法で完全に一般的に行うことができます。

于 2012-05-03T11:57:00.767 に答える
1

DEFINE_LISTリストに「名前を付ける」ことができるように、マクロに2番目の引数をいつでも追加できます。例えば:

#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つの異なるヘッダーファイルが互いにインクルードし、両方がDEFINE_LISTマクロを使用する場合は、同じリスト「名前」を再利用しないように制限する必要があります。LIST_CREATEまた、などを使用する場合は、名前でリストを参照する必要があります。

作成した関数にリストを渡すときは、ユーザー定義の「名前付き」バージョンがキャストされる「ジェネリック」型をいつでも作成できます。の実際の情報structは同じままであり、「name」タグはバイナリの観点ではなく、宣言からタイプを区別するだけなので、これは何にも影響を与えないはずです。たとえば、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され、他のユーザーが追加しないプライベートヘッダーファイルに作成されるバージョンを制御できます(必要な場合を除く)したがって、独自のタイプの場合)...しかし、これは、一般的なリストタイプの宣言と定義を実際のユーザー定義のリストタイプから分離するためのメカニズムになります。

于 2012-02-22T20:12:32.173 に答える
0

図書館を利用してみませんか?私はGLibを使用するのが好きですが、GLibによって提供されるデータ型のタイプセーフバージョンを取得するために、コード内のvoidポインターが嫌いです。いくつかの非常に単純なマクロをコーディングしました。

http://pastebin.com/Pc0KsadV

Symbol *のリストが必要な場合(以前に定義したタイプであると想定)、次のことを行う必要があります。

GLIST_INSTANCE(SymbolList, Symbol*, symbol_list);

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

于 2012-02-22T20:05:28.583 に答える
0

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

これは基本的にC++が自動的に行うことです...インスタンスを複製しています...

于 2012-02-22T20:07:31.447 に答える
0

存在のチェックと (構造体の) 定義を 1 つのマクロで実行できるとは思えません。の前にもう一度#ifndefチェックを入れDEFINE_LIST(int)ます。エレガントではありませんが、必要なことは行います。

于 2012-02-22T21:30:02.493 に答える