ジェネリックラッパー
CoddyのCジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 50/61。
void*だけで任意のデータを格納できますが、重大な欠点があります。格納されている型を追跡できなくなるのです。本当に役立つ汎用コンテナを構築するには、データポインターと型情報を組み合わせる必要があります。そこで登場するのが、Generic Wrapperパターンです。
考え方は単純です。void* と格納されている型を識別する enum をまとめる struct を作成します。これにより、ラッパーは常にその contents をどのように解釈すればよいかを把握できます。
typedef enum {
TYPE_INT,
TYPE_STRING
} DataType;
typedef struct {
void* data;
DataType type;
} Wrapper;dataを保存する際は、実際の値のためにmemoryをallocateし、その型を記録します。取得する際は、キャストする前に型を確認します。
Wrapper wrap_int(int value) {
int* p = malloc(sizeof(int));
*p = value;
return (Wrapper){ .data = p, .type = TYPE_INT };
}
void print_wrapper(Wrapper* w) {
if (w->type == TYPE_INT) {
printf("%d\n", *(int*)w->data);
} else if (w->type == TYPE_STRING) {
printf("%s\n", (char*)w->data);
}
}このパターンは、Cで型安全なGenericコンテナを構築するための基盤です。enumは実行時型タグとして機能し、格納されたdataの扱い方について、コードが安全な判断を下せるようにします。
チャレンジ
簡単後で安全に data を取得して print できるよう、integer または string のいずれかを格納し、保持している型を追跡する、type-safe な Generic wrapper システムを構築しましょう。
コードを次の 3 つのファイルに分けて整理します。
wrapper.h:TYPE_INTとTYPE_STRINGを値として持つDataTypeenum を Define します。次に、void*data pointer とDataTypefield をまとめるWrapperstruct を Define します。wrapper を Create してその contents を print するための Function prototypes を Declare します。wrapper.c:wrapper の機能を Implement します。wrap_int:integer を受け取り、そのための memory を Allocate し、value を Store して、appropriately な type tag を持つ Wrapper を返します。wrap_string:string(char*)を受け取り、memory を Allocate して string をコピーし、string type tag を持つ Wrapper を返します。print_wrapper:type tag を Check し、data を appropriately に print します(integer はそのまま、string はそのまま)。free_wrapper:wrapper 内部で dynamically Allocate された data を Free します。
main.c:すべてをまとめます。value に続いて type indicator(integer の場合はi、string の場合はs)を読み取ります。適切な wrapper を Create し、その contents を print して memory を Free します。
プログラムは 2 つの入力を受け取ります。
- type indicator:integer の場合は
i、string の場合はs - wrap する value
入力が i と 42 の場合の出力例:
42入力が s と Hello の場合の出力例:
Hello入力が i と -100 の場合の出力例:
-100入力が s と Generic Programming の場合の出力例:
Generic Programmingheader file では include guards を使用してください。string のコピーでは、strlen と strcpy を使用して、string と null terminator の分に十分な space を Allocate してください。
自分で試してみよう
#include <stdio.h>
#include <string.h>
#include "wrapper.h"
int main() {
char type;
scanf("%c", &type);
getchar(); // 改行を消費する
Wrapper w;
if (type == 'i') {
int value;
scanf("%d", &value);
// TODO: wrap_int を使用して整数ラッパーを作成する
} else if (type == 's') {
char str[256];
fgets(str, sizeof(str), stdin);
// 末尾の改行があれば削除する
str[strcspn(str, "\n")] = '\0';
// TODO: wrap_string を使用して文字列ラッパーを作成する
}
// TODO: print_wrapper を使用してラッパーの内容を出力する
// TODO: free_wrapper を使用してラッパーのメモリを解放する
return 0;
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
自分で練習してみよう: Cオンラインコンパイラ