제네릭 래퍼
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은 런타임 타입 태그 역할을 하여, 코드가 저장된 데이터를 처리하는 방법에 대해 안전한 결정을 내릴 수 있도록 돕습니다.
챌린지
쉬움정수 또는 문자열을 저장할 수 있고, 나중에 데이터를 안전하게 검색하고 출력할 수 있도록 어떤 타입을 보유하고 있는지 추적하는 타입 안전(type-safe) 제네릭 래퍼 시스템을 구축해 보겠습니다.
코드는 다음 세 개의 파일로 구성됩니다:
wrapper.h:TYPE_INT와TYPE_STRING값을 가지는DataType열거형(enum)을 정의합니다. 그런 다음void*데이터 포인터와DataType필드를 묶는Wrapper구조체를 정의합니다. 래퍼를 생성하고 그 내용을 출력하기 위한 함수 프로토타입을 선언합니다.wrapper.c: 래퍼 기능을 구현합니다:wrap_int— 정수를 받아 메모리를 할당하고 값을 저장한 뒤, 적절한 타입 태그와 함께 Wrapper를 반환합니다.wrap_string— 문자열(char*)을 받아 메모리를 할당하고 문자열을 복사한 뒤, 문자열 타입 태그와 함께 Wrapper를 반환합니다.print_wrapper— 타입 태그를 확인하고 데이터를 적절하게 출력합니다 (정수는 정수대로, 문자열은 문자열대로).free_wrapper— 래퍼 내부에서 동적으로 할당된 데이터를 해제합니다.
main.c: 모든 것을 하나로 합칩니다. 타입 표시자(정수의 경우i, 문자열의 경우s)와 그 뒤에 오는 값을 읽습니다. 적절한 래퍼를 생성하고, 그 내용을 출력한 다음 메모리를 해제합니다.
프로그램은 두 개의 입력을 받습니다:
- 타입 표시자: 정수의 경우
i또는 문자열의 경우s - 래핑할 값
입력이 i와 42일 때의 예시 출력:
42입력이 s와 Hello일 때의 예시 출력:
Hello입력이 i와 -100일 때의 예시 출력:
-100입력이 s와 Generic Programming일 때의 예시 출력:
Generic Programming헤더 파일에서 인클루드 가드(include guards)를 사용하는 것을 잊지 마세요. 문자열 복사의 경우, strlen과 strcpy를 사용하여 문자열과 널 종료 문자(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;
}
이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.