ジェネリックラッパー
CoddyのCジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 50/61。
void*だけであらゆるデータを格納できますが、重大な欠点があります。それは、どの型を保持しているのか分からなくなることです。本当に役立つ汎用コンテナを構築するには、データポインターを型情報と組み合わせる必要があります。そこで登場するのが汎用ラッパーパターンです。
考え方は単純です。void* と、格納されている型を識別する enum をまとめた struct を作成します。これにより、ラッパーは常にその内容をどのように解釈すべきか把握できます。
typedef enum {
TYPE_INT,
TYPE_STRING
} DataType;
typedef struct {
void* data;
DataType type;
} Wrapper;データを保存するときは、実際の値のためにメモリを割り当て、その型を記録します。取得するときは、キャストする前に型を確認します。
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で型安全なジェネリックコンテナを構築するための基盤です。enumは実行時型タグとして機能し、格納されたデータの扱い方について、コードが安全な判断を行えるようにします。
チャレンジ
簡単整数または文字列のいずれかを格納でき、保持している型を追跡することで、後からデータを安全に取得して出力できる、型安全なジェネリックラッパーシステムを構築しましょう。
コードを3つのファイルに分けて整理します。
wrapper.h:TYPE_INTとTYPE_STRINGの値を持つDataType列挙型を定義します。次に、void*データポインターとDataTypeフィールドをまとめたWrapper構造体を定義します。ラッパーを作成し、その内容を出力する関数のプロトタイプを宣言します。wrapper.c:ラッパーの機能を実装します。wrap_int:整数を受け取り、そのためのメモリを確保して値を格納し、適切な型タグを持つWrapperを返しますwrap_string:文字列(char*)を受け取り、メモリを確保して文字列をコピーし、文字列型のタグを持つWrapperを返しますprint_wrapper:型タグを確認し、データを適切に出力します(整数はそのまま、文字列もそのまま)free_wrapper:ラッパー内部で動的に確保されたデータを解放します
main.c:すべてをまとめます。型インジケーター(整数の場合はi、文字列の場合はs)に続けて値を読み取ります。適切なラッパーを作成し、その内容を出力して、メモリを解放します。
プログラムは2つの入力を受け取ります。
- 型インジケーター:整数の場合は
i、文字列の場合はs - ラップする値
入力がiと42の場合の出力例:
42入力がsとHelloの場合の出力例:
Hello入力がiと-100の場合の出力例:
-100入力がsとGeneric Programmingの場合の出力例:
Generic Programmingヘッダーファイルではインクルードガードを使用することを忘れないでください。文字列をコピーするには、strlenとstrcpyを使用して、文字列に加えてヌル終端文字を格納できる十分な領域を確保してください。
自分で試してみよう
#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オンラインコンパイラ