1. ホーム
  2. c

[解決済み] C言語によるテンプレートのシミュレーション(待ち行列データ型用)

2022-03-06 02:56:50

質問

を実装しようとしています。 queue 私の実装は非常にシンプルで、キューに格納できるのは int であり、それ以外には何もありません。をシミュレートできないかと考えていました。 C++ のテンプレートは C (おそらくプリプロセッサーで #define を使用することで、私の queue は任意のデータ型を保持することができます。

を使いたくないのです。 void* . ちょっと危険だし、奇妙なランタイムエラーを起こしやすいと思うのです。

どのように解決するのですか?

そのようなテンプレートを作成するためには、微妙な、あるいは醜いトリックを使うことができます。私ならこうします。

テンプレート化されたリストの作成

リストを定義するマクロ

まず、マクロを作成します。 define_list(type) - この関数は、与えられた型のリストに対するすべての関数を作成します。そして、リストのすべての関数へのポインタを含むグローバル構造体を作成し、リストの各インスタンスにそのグローバル構造体へのポインタを持たせます(この点が 仮想メソッド表 ). こんな感じです。

#define define_list(type) \
\
    struct _list_##type; \
    \
    typedef struct \
    { \
        int (*is_empty)(const struct _list_##type*); \
        size_t (*size)(const struct _list_##type*); \
        const type (*front)(const struct _list_##type*); \
        void (*push_front)(struct _list_##type*, type); \
    } _list_functions_##type; \
    \
    typedef struct _list_elem_##type \
    { \
        type _data; \
        struct _list_elem_##type* _next; \
    } list_elem_##type; \
    \
    typedef struct _list_##type \
    { \
        size_t _size; \
        list_elem_##type* _first; \
        list_elem_##type* _last; \
        _list_functions_##type* _functions; \
    } List_##type; \
    \
    List_##type* new_list_##type(); \
    bool list_is_empty_##type(const List_##type* list); \
    size_t list_size_##type(const List_##type* list); \
    const type list_front_##type(const List_##type* list); \
    void list_push_front_##type(List_##type* list, type elem); \
    \
    bool list_is_empty_##type(const List_##type* list) \
    { \
        return list->_size == 0; \
    } \
    \
    size_t list_size_##type(const List_##type* list) \
    { \
        return list->_size; \
    } \
    \
    const type list_front_##type(const List_##type* list) \
    { \
        return list->_first->_data; \
    } \
    \
    void list_push_front_##type(List_##type* list, type elem) \
    { \
        ... \
    } \
    \
    _list_functions_##type _list_funcs_##type = { \
        &list_is_empty_##type, \
        &list_size_##type, \
        &list_front_##type, \
        &list_push_front_##type, \
    }; \
    \
    List_##type* new_list_##type() \
    { \
        List_##type* res = (List_##type*) malloc(sizeof(List_##type)); \
        res->_size = 0; \
        res->_first = NULL; \
        res->_functions = &_list_funcs_##type; \
        return res; \
    }

#define List(type) \
    List_##type

#define new_list(type) \
    new_list_##type()

汎用インターフェース

ここでは、格納された関数ポインタを介してリストの関数を単純に呼び出すマクロをいくつか紹介します。

#define is_empty(collection) \
    collection->_functions->is_empty(collection)

#define size(collection) \
    collection->_functions->size(collection)

#define front(collection) \
    collection->_functions->front(collection)

#define push_front(collection, elem) \
    collection->_functions->push_front(collection, elem)

同じ構造を使ってリスト以外のコレクションを設計する場合、良いポインタを格納するどのコレクションに対しても最後の関数を使用することができることに注意してください。

使用例

そして最後に、新しいリスト・テンプレートの使い方を示す小さな例を挙げます。

/* Define the data structures you need */
define_list(int)
define_list(float)

int main()
{
    List(int)* a = new_list(int);
    List(float)* b = new_list(float);

    push_front(a, 5);
    push_front(b, 5.2);
}

Cのテンプレートを本当に使いたいのなら、その程度のトリックは使えますが、それはかなり醜いです(C++を使えばいいのです、もっとシンプルになります)。唯一のオーバーヘッドは、データ構造のインスタンスごとにポインタが1つ増えることと、関数を呼び出すときに間接的に1つ増えることです(キャストは行われません。 void* のポインターがあるんだよ。) 使わないことを祈る:p

制限事項

もちろん、本物のテンプレートではなく、単なるテキスト置換マクロを使用しているため、いくつかの制限があります。

一度定義する

各タイプはコンパイル単位に一度だけ定義することができます。これは大きな欠点となります。例えば、ライブラリを書いていて、ヘッダの中に define_ という命令があります。

マルチワード型

を作成したい場合は List そのテンプレート・タイプが複数の単語で構成されている ( signed char , unsigned long , const bar , struct foo ...) または、テンプレート・タイプがポインタである ( char* , void* ...)を使用する場合は typedef という型があります。

define_list(int) /* OK */
define_list(char*) /* Error: pointer */
define_list(unsigned long) /* Error: several words */

typedef char* char_ptr;
typedef unsigned long ulong;
define_list(char_ptr) /* OK */
define_list(ulong) /* OK */

ネストされたリストを作成する場合も、同じトリックを使う必要があります。