この質問にとって重要ではないいくつかの理由により、ANSI の一般的な構造体のリスト (コンテナー) を実装することは可能ですか? この同じリストに異なる型の構造体を含めることはできますか?
編集:いくつかの提案の後、いくつかのオプションを検討しています。しかし、これを行うための標準的な方法がない理由はまだはっきりしていません。ANSI Cでは必要ありませんか? それを行う唯一の方法は、別の言語を使用することですか?
乾杯
この質問にとって重要ではないいくつかの理由により、ANSI の一般的な構造体のリスト (コンテナー) を実装することは可能ですか? この同じリストに異なる型の構造体を含めることはできますか?
編集:いくつかの提案の後、いくつかのオプションを検討しています。しかし、これを行うための標準的な方法がない理由はまだはっきりしていません。ANSI Cでは必要ありませんか? それを行う唯一の方法は、別の言語を使用することですか?
乾杯
あなたがやりたいことに応じて、それは可能です。それはお尻の大きな痛みですが、それは可能です。
void
ポインタを使用して、次のような汎用リストを作成できます。
struct glist {
void *data;
stuct glist *next;
};
メンバーは、任意のタイプのdata
オブジェクトを指すことができます。問題は、ポインタを割り当てるとその型情報が失われることです。タイプを個別に追跡する必要があります。整数値または列挙値を使用して、次のような型にタグを付けることができます。
enum dtype {type_A, type_B, type_C, ...};
struct glist {
void *data;
enum dtype data_type;
struct glist *next;
};
また
#define TYPE_A 1
#define TYPE_B 2
...
struct glist {
void *data;
int data_type;
struct glist *next;
};
リストから要素を取得するときは、data_type
メンバーをチェックして、データ型に基づいて呼び出す関数または実行するプロシージャを決定します。
switch (elem->data_type)
{
case TYPE_A: // process for TYPE A
break;
case TYPE_B: // process for TYPE B
break;
...
}
または、その特定のデータ型を操作する1つ以上の関数へのポインターを格納し、スイッチまたはif-elseステートメントを完全にスキップすることもできます。
struct glist {
void *data;
void (*process)(const void *);
void (*copy)(const void *);
void (*delete)(const void *); // not shown
struct glist *next;
};
データ型ごとに、プロセス関数のインスタンスを作成し、その関数へのポインターをノードに関連付けます。関数はvoid *
入力としてを受け取る必要がありますが、処理を行うために適切なタイプに変換されます。
void process_typeA (const void *data)
{
typeA *obj = (typeA *) data; // cast away const
// process obj as necessary
}
void *copy_typeA (const void *data)
{
typeA *obj = (typeA *) data;
typeA *newObj = malloc(sizeof *newObj);
if (newObj)
// copy from obj to newObj
return newObj;
}
void addItem (struct glist *list, const void *obj,
void (*process)(const void *),
void *(*copy)(consg void *),
void (*delete)(const void *))
{
struct glist *newNode = malloc(sizeof *newNode);
newNode->copy = copy;
newNode->delete = delete;
newNode->process = process;
newNode->data = (*copy)(obj);
newNode->next = NULL;
// add newNode to list
}
void foo(void)
{
struct glist *myList = new_list(); // not shown here
...
addItem(myList, myTypeAObj, process_typeA, copy_typeA, delete_typeA);
addItem(myList, myTypeBObj, process_typeB, copy_typeB, delete_typeB);
...
}
process
次に、リストをウォークスルーしながら、その特定のノードに適した関数を呼び出すことができます。
void processList(struct glist *list)
{
struct glist *node = list;
while (node)
{
node->process(node->data);
node = node->next;
}
...
}
この方法の利点は、そのノードの動作をノード自体に関連付けることです。スイッチやif-elseチェーンにケースを追加し続ける必要はありません。欠点は、多くのポインタをいじくり回していることです。操作するデータ型ごとに個別のプロセス/コピー/削除機能を実装する必要があり、重要な量のメモリ管理を行っています(コピーと削除のメソッドが必要です。理由は、考えてみれば明らかです)。そして、タイプセーフティの概念をウィンドウの外に投げ出し、対向するトラフィックに投げ込みます。間違ったプロセス/コピー/削除関数を間違ったデータ型に関連付けることに対するコンパイラレベルの保護が失われます。
Linux Kernel Linked List を読みたいと思うかもしれません。(http://isis.poly.edu/kulesh/stuff/src/klist/ には情報があります)
任意の構造体を格納できますが、特定のリストには 1 つの型しかないと思います。使用してからしばらく経っているので、細部が少し錆びています。Richard がほのめかしているように、プリプロセッサ ブードゥー教を使用していますが、多すぎません。
sglibを見たことがありますか?
Jacob Navia は、「The C Containers library project」についてこの計画を立てています。