Pimpl 이디엄
Coddy C++ 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨 — 104개 중 78번째.
Pimpl 이디엄(Pointer to Implementation)은 클래스의 구현 세부 사항을 별도의 전방 선언된 클래스로 이동시켜 숨기는 기법입니다. 이 기법은 컴파일 의존성을 줄이고 프라이빗(private) 멤버를 헤더 파일로부터 완전히 숨겨줍니다.
핵심 아이디어는 간단합니다. 클래스 내부에 프라이빗 멤버를 직접 선언하는 대신, 소스 파일에만 정의된 구현 클래스에 대한 포인터를 선언하는 것입니다:
// Widget.h
#include <memory>
class Widget {
public:
Widget();
~Widget();
void doSomething();
private:
class Impl; // 전방 선언
std::unique_ptr<Impl> pImpl;
};// Widget.cpp
#include "Widget.h"
#include <iostream>
class Widget::Impl {
public:
int data = 42;
void process() { std::cout << "Processing: " << data << "\n"; }
};
Widget::Widget() : pImpl(std::make_unique<Impl>()) {}
Widget::~Widget() = default;
void Widget::doSomething() { pImpl->process(); }주요 이점은 컴파일 방화벽(compilation firewall)과 바이너리 호환성(binary compatibility)입니다. Impl 클래스를 변경할 때, 헤더를 포함하는 모든 파일이 아닌 소스 파일만 다시 컴파일하면 됩니다. 이는 대규모 프로젝트에서 빌드 시간을 획기적으로 단축합니다.
소멸자는 소스 파일에 정의되어야 함에 유의하십시오 (기본값으로 설정된 경우라도). 이는 unique_ptr가 이를 삭제하기 위해 Impl의 완전한 타입(complete type)을 필요로 하기 때문입니다. 이것은 Pimpl을 처음 사용할 때 흔히 발생하는 실수입니다.
챌린지
쉬움Pimpl 관용구(Pimpl idiom)를 사용하여 헤더 파일에서 암호화 구현 세부 사항을 숨기는 보안 메시지 핸들러를 만들어 보겠습니다. 이는 Pimpl이 어떻게 컴파일 방화벽(compilation firewall)을 생성하는지 보여줍니다. 여러분의 헤더를 포함하는 누구라도 메시지 처리의 내부 작동 방식을 볼 수 없게 됩니다.
코드는 세 개의 파일로 구성됩니다:
SecureMessage.h:SecureMessage클래스의 공개 인터페이스를 정의합니다.클래스에는 전방 선언된
Impl클래스와 이에 대한std::unique_ptr가 있어야 합니다. 공개 인터페이스에는 다음이 포함되어야 합니다:- 원본 메시지를 위한
const std::string&를 인자로 받는 생성자 - 소멸자 (여기서 선언하고, .cpp 파일에서 정의해야 함)
- 암호화 키를 설정하는
setKey(int key)메서드 - 암호화된 메시지를
std::string으로 반환하는getEncrypted()메서드 - 원본 메시지를 반환하는
getOriginal()메서드
헤더에는 공개 인터페이스만 표시되어야 하며, 암호화가 어떻게 작동하는지에 대한 구현 세부 사항은 여기에 노출되어서는 안 됩니다.
- 원본 메시지를 위한
SecureMessage.cpp: 중첩된Impl클래스를 정의하고 모든 메서드를 구현합니다.Impl클래스는 원본 메시지, 암호화 키(기본값 0)를 저장하고 실제 암호화 로직을 처리해야 합니다. 암호화에는 간단한 카이사르 암호(Caesar cipher)를 사용하세요: 각 문자를 키 값만큼 이동시킵니다. 예를 들어, 키가 3이면 'a'는 'd'가 되고, 'z'는 순환하여 'c'가 됩니다.암호화는 소문자(a-z)에만 적용되어야 하며, 다른 모든 문자는 변경되지 않은 상태로 두어야 합니다.
unique_ptr가 완전한Impl타입을 필요로 하므로 소멸자를 여기서 정의(기본값으로 설정하더라도)해야 함을 기억하세요.main.cpp: 두 개의 입력을 읽습니다:- 메시지 문자열 (공백을 포함할 수 있음)
- 암호화 키 (정수)
SecureMessage객체를 생성하고, 키를 설정한 후 결과를 표시합니다:Original:뒤에 원본 메시지를 출력합니다.Encrypted:뒤에 암호화된 메시지를 출력합니다.
예를 들어, 입력값이 hello world와 3인 경우:
Original: hello world
Encrypted: khoor zruog입력값이 xyz abc와 5인 경우:
Original: xyz abc
Encrypted: cde fgh헤더 파일이 카이사르 암호 구현에 대해 아무것도 드러내지 않는 방식에 주목하세요. 이것이 Pimpl의 힘입니다. 나중에 다른 암호화 알고리즘으로 변경하더라도 헤더를 포함하는 파일이 아닌 SecureMessage.cpp만 다시 컴파일하면 됩니다.
치트 시트
Pimpl 이디엄(Pointer to Implementation)은 구현 세부 사항을 소스 파일에만 정의된 별도의 클래스로 이동시켜 구현 세부 사항을 숨기고, 컴파일 방화벽(compilation firewall)을 생성합니다.
헤더에서의 전방 선언을 포함한 기본 구조:
// Widget.h
#include <memory>
class Widget {
public:
Widget();
~Widget(); // 헤더에서 선언하고 .cpp에서 정의해야 함
void doSomething();
private:
class Impl; // 전방 선언
std::unique_ptr<Impl> pImpl;
};소스 파일에서의 구현:
// Widget.cpp
#include "Widget.h"
class Widget::Impl {
public:
int data = 42;
void process() { /* 구현부 */ }
};
Widget::Widget() : pImpl(std::make_unique<Impl>()) {}
Widget::~Widget() = default; // 반드시 .cpp에서 정의되어야 함
void Widget::doSomething() { pImpl->process(); }주요 포인트:
- 소멸자는 소스 파일에서 정의되어야 합니다(기본값으로 설정하더라도).
unique_ptr가Impl을 삭제하기 위해Impl의 완전한 타입(complete type)을 필요로 하기 때문입니다. Impl에 대한 변경 사항은 헤더를 포함하는 파일이 아닌 소스 파일만 다시 컴파일하면 됩니다.- 컴파일 방화벽과 바이너리 호환성을 제공합니다.
직접 해보기
#include <iostream>
#include <string>
#include "SecureMessage.h"
int main() {
// 메시지 읽기 (공백을 포함할 수 있음)
std::string message;
std::getline(std::cin, message);
// 암호화 키 읽기
int key;
std::cin >> key;
// TODO: 메시지를 사용하여 SecureMessage 객체 생성
// TODO: 암호화 키 설정
// TODO: "Original: " 뒤에 원본 메시지를 출력
// TODO: "Encrypted: " 뒤에 암호화된 메시지를 출력
return 0;
}
이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.