[C++] 항목 41: 템플릿 프로그래밍의 천릿길도 암시적 인터페이스와 컴파일 타임 다형성부터

업데이트:     Updated:

카테고리:

태그:

이 글은 아래의 책을 자세히 정리한 후, 정리한 글을 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 타입이 반드시 만족해야 할 것은?

일반적인 착각:

  1. ❌ 정수 값을 반환하는 size 함수를 지원해야 한다?
    • 이유: T의 operator>를 재정의하는 경우가 있기 때문
  2. ❌ T 타입의 객체 둘을 비교하는 operator!= 함수를 지원해야 한다?
    • 이유: 타입 변환을 통해 T에 operator!=가 없어도 유효한 호출이 될 수 있기 때문


실제로 필요한 것:

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()를 가져야 함
                         // 컴파일 타임에 검증
}

🧐 정리

  1. 클래스 및 템플릿은 모두 인터페이스와 다형성을 지원한다.
    하지만 그 방식이 근본적으로 다르다.
  2. 클래스의 경우:
    • 인터페이스는 명시적이며 함수의 시그너처를 중심으로 구성되어 있다
    • 다형성은 프로그램 실행 중에 가상 함수를 통해 나타난다
  3. 템플릿 매개변수의 경우:
    • 인터페이스는 암시적이며 유효 표현식에 기반을 두어 구성된다
    • 다형성은 컴파일 중에 템플릿 인스턴스화와 함수 오버로딩 모호성 해결을 통해 나타난다
  4. 암시적 인터페이스는 유연하지만 명확성이 떨어질 수 있다.
    템플릿 사용 시 필요한 인터페이스를 문서화하거나 concept(C++20)을 사용하여 명확히 하자.

Cpp 카테고리 내 다른 글 보러가기

댓글남기기