[C++] 항목 41: 템플릿 프로그래밍의 천릿길도 암시적 인터페이스와 컴파일 타임 다형성부터
카테고리: Cpp
태그: Cpp
이 글은 아래의 책을 자세히 정리한 후, 정리한 글을 GPT에게 요약을 요청하여 작성되었습니다.
이펙티브 C++ 제3판, 스콧 마이어스 저자, 곽용재 번역
📦 7. 템플릿과 일반화 프로그래밍
👉🏻 항목 41: 템플릿 프로그래밍의 천릿길도 암시적 인터페이스와 컴파일 타임 다형성부터
✅ 클래스 기반 프로그래밍: 명시적 인터페이스와 런타임 다형성
class Widget {
public:
	Widget();
	virtual ~Widget();
	virtual size_t size() const;
	virtual void normalize();
	void swap(Widget& other);
	...
};
void doProcessing(Widget& w) {
	if(w.size() > 10 && w != someNastyWidget) {
		Widget temp(w);
		temp.normalize();
		temp.swap(w);
	}
}
명시적 인터페이스 (Explicit Interface)
- 함수 이름이나 시그너처로 드러나는 인터페이스
 - 함수 시그너처: 함수의 이름, 매개변수 타입, 반환 타입 등을 통틀어 부르는 용어
 - w는 Widget 타입으로 명확히 드러나 있다
 
런타임 다형성 (Runtime Polymorphism)
- 실행 중에 결정되는 다형성
 - Widget 클래스에는 가상 함수가 있다
 - 가상 함수는 런타임 시점에 가상 테이블을 통해 맵핑된다
→ 동적 바인딩 
✅ 템플릿 기반 프로그래밍: 암시적 인터페이스와 컴파일 타임 다형성
template<typename T>
void doProcessing(T& w) {
	if(w.size() > 10 && w != someNastyWidget) {
		T temp(w);
		temp.normalize();
		temp.swap(w);
	}
}
일반화 프로그래밍 (Generic Programming)
데이터 타입에 상관없이 동작할 수 있는 코드를 작성하는 프로그래밍 기법
핵심 개념:
- 암시적 인터페이스
 - 컴파일 타임 다형성
 
암시적 인터페이스 (Implicit Interface)
- 코드의 사용 형태를 통해 컴파일러가 자동으로 요구 사항을 추론하는 인터페이스
 - w의 자료형 T는 다음을 지원해야 한다:
    
- size, normalize, swap
 - 복사 생성자
 - 비교 연산
 
 - 명시적으로 선언되지 않지만, 사용 패턴으로부터 추론됨
 
컴파일 타임 다형성 (Compile-time Polymorphism)
- 컴파일 중에 결정되는 다형성
 - w가 수반되는 함수 호출이 일어날 때, 해당 호출을 성공시키기 위해 템플릿의 인스턴스화가 일어난다
 - 이러한 인스턴스화가 일어나는 시점은 컴파일 도중이다
 - 어떤 타입 T로 호출될지 컴파일러가 알아야 실제 함수 코드를 생성할 수 있음
 
📌 암시적 인터페이스의 유연성
template<typename T>
void doProcessing(T& w) {
	if(w.size() > 10 && w != someNastyWidget) {
		...
	}
}
암시적 인터페이스의 핵심:
암시적 인터페이스를 이루는 요소는 유효 표현식이다. 즉, 표현식만 만족시키면 된다.
위 코드에서 필요한 표현식: w.size() > 10 && w != someNastyWidget
🤔 T 타입이 반드시 만족해야 할 것은?
일반적인 착각:
- ❌ 정수 값을 반환하는 size 함수를 지원해야 한다?
    
- 이유: T의 
operator>를 재정의하는 경우가 있기 때문 
 - 이유: T의 
 - ❌ T 타입의 객체 둘을 비교하는 
operator!=함수를 지원해야 한다?- 이유: 타입 변환을 통해 T에 
operator!=가 없어도 유효한 호출이 될 수 있기 때문 
 - 이유: 타입 변환을 통해 T에 
 
실제로 필요한 것:
if문 안의 최종 값이 bool과 호환되기만 하면 된다
📊 명시적 인터페이스 vs 암시적 인터페이스
| 특성 | 명시적 인터페이스 | 암시적 인터페이스 | 
|---|---|---|
| 정의 방식 | 함수 시그너처로 명확히 정의 | 사용 패턴으로 추론 | 
| 타입 명시 | 구체적 타입 (Widget) | 템플릿 매개변수 (T) | 
| 요구사항 | 특정 함수와 시그너처 | 유효 표현식 | 
| 유연성 | 낮음 (고정된 타입) | 높음 (다양한 타입 가능) | 
| 검증 시점 | 컴파일 타임 | 컴파일 타임 | 
📊 런타임 다형성 vs 컴파일 타임 다형성
| 특성 | 런타임 다형성 | 컴파일 타임 다형성 | 
|---|---|---|
| 구현 방법 | 가상 함수, 가상 테이블 | 템플릿 인스턴스화 | 
| 결정 시점 | 프로그램 실행 중 | 컴파일 중 | 
| 바인딩 | 동적 바인딩 | 정적 바인딩 | 
| 성능 비용 | 간접 호출 비용 | 코드 크기 증가 | 
| 유연성 | 런타임 타입 결정 | 컴파일 타임 타입 결정 | 
🔍 공통점: 컴파일 타임 검증
중요한 공통점:
암시적 인터페이스와 명시적 인터페이스는 컴파일 도중에 점검된다는 것은 동일하다.
🚨 주의점: 인터페이스 불일치
template<typename T>
void doProcessing(T& w) {
	if(w.size() > 10 && w != someNastyWidget) {
		T temp(w);
		temp.normalize();
		temp.swap(w);
	}
}
// 사용 예시
class MyClass {
	// size() 함수가 없음!
};
int main() {
	MyClass obj;
	doProcessing(obj); // ❌ 컴파일 에러
}
문제:
- 템플릿 안에서 A 객체를 쓸 때,
템플릿에서 요구하는 인터페이스를 A 객체가 지원하지 않으면 사용이 불가능하다 - 아예 컴파일이 안 된다
 
💡 실용적인 이해
클래스 기반 설계:
class Animal {
public:
    virtual void makeSound() = 0;  // 명시적 인터페이스
};
class Dog : public Animal {
public:
    void makeSound() override { /* 런타임에 결정 */ }
};
템플릿 기반 설계:
template<typename T>
void letItSpeak(T& animal) {
    animal.makeSound();  // 암시적 인터페이스
                         // T가 makeSound()를 가져야 함
                         // 컴파일 타임에 검증
}
🧐 정리
- 클래스 및 템플릿은 모두 인터페이스와 다형성을 지원한다.
하지만 그 방식이 근본적으로 다르다. - 클래스의 경우:
    
- 인터페이스는 명시적이며 함수의 시그너처를 중심으로 구성되어 있다
 - 다형성은 프로그램 실행 중에 가상 함수를 통해 나타난다
 
 - 템플릿 매개변수의 경우:
    
- 인터페이스는 암시적이며 유효 표현식에 기반을 두어 구성된다
 - 다형성은 컴파일 중에 템플릿 인스턴스화와 함수 오버로딩 모호성 해결을 통해 나타난다
 
 - 암시적 인터페이스는 유연하지만 명확성이 떨어질 수 있다.
템플릿 사용 시 필요한 인터페이스를 문서화하거나 concept(C++20)을 사용하여 명확히 하자. 
댓글남기기