Menu
Coddy logo textTech

제네릭 래퍼

Coddy C 여정의 Object Oriented Programming 섹션에 포함된 레슨 — 61개 중 50번째.

void* 하나만으로는 어떤 데이터든 저장할 수 있지만, 치명적인 결함이 있습니다. 바로 어떤 타입을 보유하고 있는지 추적할 수 없게 된다는 점입니다. 진정으로 유용한 제네릭 컨테이너를 구축하려면, 데이터 포인터를 타입 정보와 결합해야 합니다. 여기서 Generic Wrapper 패턴이 등장합니다.

아이디어는 간단합니다. 저장된 타입을 식별하는 enum과 void*를 하나로 묶는 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은 런타임 타입 태그 역할을 하여, 코드가 저장된 데이터를 처리하는 방법에 대해 안전한 결정을 내릴 수 있도록 돕습니다.

challenge icon

챌린지

쉬움

정수 또는 문자열을 저장할 수 있고, 나중에 데이터를 안전하게 검색하고 출력할 수 있도록 어떤 타입을 보유하고 있는지 추적하는 타입 안전(type-safe) 제네릭 래퍼 시스템을 구축해 보겠습니다.

코드는 다음 세 개의 파일로 구성됩니다:

  • wrapper.h: TYPE_INTTYPE_STRING 값을 가지는 DataType 열거형(enum)을 정의합니다. 그런 다음 void* 데이터 포인터와 DataType 필드를 묶는 Wrapper 구조체를 정의합니다. 래퍼를 생성하고 그 내용을 출력하기 위한 함수 프로토타입을 선언합니다.
  • wrapper.c: 래퍼 기능을 구현합니다:
    • wrap_int — 정수를 받아 메모리를 할당하고 값을 저장한 뒤, 적절한 타입 태그와 함께 Wrapper를 반환합니다.
    • wrap_string — 문자열(char*)을 받아 메모리를 할당하고 문자열을 복사한 뒤, 문자열 타입 태그와 함께 Wrapper를 반환합니다.
    • print_wrapper — 타입 태그를 확인하고 데이터를 적절하게 출력합니다 (정수는 정수대로, 문자열은 문자열대로).
    • free_wrapper — 래퍼 내부에서 동적으로 할당된 데이터를 해제합니다.
  • main.c: 모든 것을 하나로 합칩니다. 타입 표시자(정수의 경우 i, 문자열의 경우 s)와 그 뒤에 오는 값을 읽습니다. 적절한 래퍼를 생성하고, 그 내용을 출력한 다음 메모리를 해제합니다.

프로그램은 두 개의 입력을 받습니다:

  1. 타입 표시자: 정수의 경우 i 또는 문자열의 경우 s
  2. 래핑할 값

입력이 i42일 때의 예시 출력:

42

입력이 sHello일 때의 예시 출력:

Hello

입력이 i-100일 때의 예시 출력:

-100

입력이 sGeneric Programming일 때의 예시 출력:

Generic Programming

헤더 파일에서 인클루드 가드(include guards)를 사용하는 것을 잊지 마세요. 문자열 복사의 경우, strlenstrcpy를 사용하여 문자열과 널 종료 문자(null terminator)를 위한 충분한 공간을 할당하십시오.

치트 시트

제네릭 래퍼(Generic Wrapper) 패턴은 void* 포인터를 타입 정보와 결합하여 타입 안전한 제네릭 컨테이너를 생성합니다. 이를 통해 어떤 데이터 타입이 저장되어 있는지에 대한 정보를 유지하면서 모든 데이터 타입을 저장할 수 있습니다.

저장된 타입을 식별하기 위한 열거형(enum)을 정의합니다:

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);
    }
}

열거형은 런타임 타입 태그 역할을 하여, 안전한 타입 체크와 저장된 데이터의 적절한 처리를 가능하게 합니다.

직접 해보기

#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실력 점검

이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.

Object Oriented Programming의 모든 레슨