Menu
Coddy logo textTech

ジェネリックラッパー

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の扱い方について、コードが安全な判断を下せるようにします。

challenge icon

チャレンジ

簡単

後で安全に data を取得して print できるよう、integer または string のいずれかを格納し、保持している型を追跡する、type-safe な Generic wrapper システムを構築しましょう。

コードを次の 3 つのファイルに分けて整理します。

  • wrapper.hTYPE_INTTYPE_STRING を値として持つ DataType enum を Define します。次に、void* data pointer と DataType field をまとめる Wrapper struct を 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 つの入力を受け取ります。

  1. type indicator:integer の場合は i、string の場合は s
  2. wrap する value

入力が i42 の場合の出力例:

42

入力が sHello の場合の出力例:

Hello

入力が i-100 の場合の出力例:

-100

入力が sGeneric Programming の場合の出力例:

Generic Programming

header file では include guards を使用してください。string のコピーでは、strlenstrcpy を使用して、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;
}
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン

自分で練習してみよう: Cオンラインコンパイラ