불투명 구조체 정의하기
Coddy C 여정의 Object Oriented Programming 섹션에 포함된 레슨 — 61개 중 18번째.
이제 불투명 포인터(opaque pointers)의 개념을 이해했으므로, 패턴을 완성하는 방법을 살펴보겠습니다. 헤더는 타입이 존재함을 선언하지만, 실제 내부 내용은 소스 파일에서 정의합니다.
전체 설정은 다음과 같습니다. 먼저, 전방 선언(forward declaration)만 포함된 헤더입니다:
// counter.h
#ifndef COUNTER_H
#define COUNTER_H
typedef struct Counter Counter; // 불완전한 타입
Counter *create_counter(int start);
void free_counter(Counter *c);
#endif
그 다음, 소스 파일에는 실제 구조체 정의가 포함됩니다:
// counter.c
#include "counter.h"
#include <stdlib.h>
struct Counter {
int value; // 외부로부터 숨겨짐!
int step;
};
Counter *create_counter(int start) {
Counter *c = malloc(sizeof(Counter));
c->value = start;
c->step = 1;
return c;
}
void free_counter(Counter *c) {
free(c);
}
핵심 포인트: struct Counter { ... }는 .c 파일에만 나타납니다. counter.h를 포함하는 모든 파일은 Counter가 존재한다는 것은 알지만 value나 step을 볼 수는 없습니다. main.c에서 c->value를 작성하려고 시도하면 컴파일러 오류가 발생합니다. 즉, 멤버들이 실제로 보이지 않게 됩니다.
이러한 분리는 C 언어에서 캡슐화의 기초가 됩니다. 다음 단계는 getter 및 setter 함수를 통해 제어된 접근을 제공하는 것입니다.
챌린지
쉬움불투명 포인터(opaque pointer) 패턴을 보여주는 Timer 모듈을 만들어 보겠습니다. 구조체 정의를 소스 파일 내부에 숨겨서, 모듈을 사용하는 어떤 코드에서도 내부 멤버를 완전히 볼 수 없게 만드는 연습을 할 것입니다.
세 개의 파일을 생성하게 됩니다:
timer.h: 이것은 공개 인터페이스입니다. 전방 선언(forward declaration)만을 사용하여 불투명한Timer타입을 선언하세요. 여기에는 구조체 본문이 없어야 합니다! 또한 두 개의 함수를 선언하세요:create_timer(초를 나타내는 정수를 받아 Timer 포인터를 반환)와free_timer(Timer 포인터를 받아 메모리를 정리).TIMER_H심볼을 사용하여 인클루드 가드(include guards)를 사용하세요.timer.c: 이곳에 비밀이 담깁니다. 두 개의 숨겨진 멤버int seconds와int running(실행 중이면 1, 중지됨이면 0 사용)을 가진 실제struct Timer를 정의하세요. 구조체를 할당하고, 제공된 값으로seconds를 초기화하며,running을 1로 설정하는 생성자를 구현하세요. 메모리를 해제하는 소멸자를 구현하세요.main.c: Timer 모듈을 사용하여 타이머를 생성하고 성공적으로 생성되었는지 확인하세요. 구조체 멤버가 숨겨져 있기 때문에 직접 접근할 수 없습니다. 이것이 바로 핵심입니다! 단순히 생성을 확인하는 메시지를 출력한 다음, 타이머를 해제하고 정리 메시지를 출력하세요.
하나의 입력을 받게 됩니다: 타이머를 초기화할 초 수(정수)입니다.
메인 파일에서 제공된 초 값으로 타이머를 생성하세요. 생성 메시지를 출력한 다음, 타이머를 해제하고 확인 메시지를 출력하세요.
출력은 다음 형식을 따르세요:
Timer created
Timer freed여기서 핵심 통찰은 main.c가 Timer 포인터를 다룰 수는 있지만 seconds나 running에 직접 접근할 방법이 없다는 것입니다. main.c에서 t->seconds를 작성하려고 시도하면 구조체 본문이 timer.c에만 정의되어 있기 때문에 컴파일러 오류가 발생합니다. 이것이 진정한 캡슐화입니다. 구현 세부 사항이 모듈 사용자로부터 완전히 숨겨집니다.
치트 시트
불투명 포인터(opaque pointer) 패턴은 타입의 선언과 정의를 분리하여 구현 세부 사항을 숨깁니다.
헤더 파일에는 전방 선언(forward declaration)만 포함됩니다:
// counter.h
#ifndef COUNTER_H
#define COUNTER_H
typedef struct Counter Counter; // 불완전한 타입
Counter *create_counter(int start);
void free_counter(Counter *c);
#endif
소스 파일에는 실제 구조체 정의가 포함됩니다:
// counter.c
#include "counter.h"
#include <stdlib.h>
struct Counter {
int value; // 외부로부터 숨겨짐
int step;
};
Counter *create_counter(int start) {
Counter *c = malloc(sizeof(Counter));
c->value = start;
c->step = 1;
return c;
}
void free_counter(Counter *c) {
free(c);
}
구조체 정의는 .c 파일에만 나타납니다. 헤더를 포함하는 파일은 해당 타입이 존재한다는 것은 알지만 멤버에 직접 접근할 수는 없습니다. 이는 C 언어에서 진정한 캡슐화를 제공합니다.
직접 해보기
#include <stdio.h>
#include "timer.h"
int main() {
int seconds;
scanf("%d", &seconds);
// TODO: 제공된 seconds 값을 사용하여 타이머를 생성하세요
// TODO: "Timer created"를 출력하세요
// TODO: 타이머를 해제하세요
// TODO: "Timer freed"를 출력하세요
return 0;
}
이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.