Notice
Recent Posts
Recent Comments
Link
«   2026/05   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31
Archives
Today
Total
관리 메뉴

로또

41. 템플릿 프로그래밍의 천릿길도 암시적 인터페이스와 컴파일 타임 다형성부 본문

책/Effective C++

41. 템플릿 프로그래밍의 천릿길도 암시적 인터페이스와 컴파일 타임 다형성부

아롱로또 2023. 12. 12. 03:13

다루는 내용

템플릿 프로그래밍에서 사용되는 개념인 암시적 인터페이스와 컴파일 타임 다형성에 대해 알아보자.

 

명시적 인터페이스와 런타임 다형성

// Widget.h
class Widget{
public:
	Widget();
	virtual ~Widget();
	virtual std::size_t size() const;
	virtual void normalize();
	void swap(Widget& widget);
	...
};
void DoProcessing(Widget& w){
	if(w.size() > 10 && w != some_nasty_widget){
		Widget temp(w);
		temp.normalize();
		temp.swap(w);
	}
}

명시적 인터페이스(explicit interface)

소스 코드에 명시적으로 드러나는 인터페이스.

  • DoProcessing()에서 w는 Widget 타입으로 선언되었으므로 w는 Widget 인터페이스를 지원해야 한다. 해당 인터페이스를 소스 코드에서 찾으면(Widget.h) 이것이 어떤 형태인지를 확인할 수 있으므로, Widget은 명시적 인터페이스이다.

런타임 다형성(run-time polymorphism)

특정한 함수에 대한 실제 호출이 런타임 중에 객체의 동적 타입을 기반으로 결정되는 것.

  • Widget의 멤버 함수 중 일부는 가상 함수로, 이들에 대한 호출은 런타임 다형성에 의해 이루어진다.

암시적 인터페이스와 컴파일 타임 다형성

위 예제의 DoProcessing 함수를 함수 템플릿으로 변경하였다.

template<typename T>
void DoProcessiong(T& w){
	if(w.size() > 10 && w != some_nasty_widget){
		T temp(w);
		temp.normalize();
		temp.swap(w);
	}
}

 

암시적 인터페이스(implicit interface)

템플릿이 컴파일되기 위해 유효해야하는 표현식(expression).

  • w가 지원해야 하는 인터페이스는 이 템플릿 안에서 w에 대해 실행되는 연산이 결정한다.
  • 즉, w는 size(), != 연산, 복사 생성자, normalize(), swap() 함수를 지원해야 한다.
  • 템플릿이 제대로 컴파일되기 위해 이러한 표현식이 유효(valid)해야 하는데, 이러한 표현식들은 T가 지원해야 하는 암시적 인터페이스이다.

컴파일 타임 다형성(compile-time polymorphism)

컴파일 타임에 템플릿의 인스턴스화가 발생할 때, 인스턴스화가 발생하는 함수 템플릿에 어떤 템플릿 매개변수가 들어가느냐에 따라 호출되는 함수가 달라지는 것.

  • operator >와 operator !=과 같이 w를 인자로 하는 함수가 호출될 때, 해당 호출을 성공시키기 위해 컴파일 타임에 템플릿의 인스턴스화가 일어난다.

명시적 인터페이스 vs 암시적 인터페이스

명시적 인터페이스는 함수 시그니처(함수의 이름, 매개변수 타입, 반환 타입, 함수 상수성 여부 등)로 이루어진다.

Widget 클래스의 public 인터페이스는 생성자, 소멸자, size, normalize, swap 함수, 컴파일러가 자동 생성한 복사 생성자, 복사 대입 연산자 등을 포함할 것이다. 또한 typedef 타입이 존재할 경우 포함될 수 있다. 반면 데이터 멤버는 시그니처에 포함되지 않는다. 

 

암시적 인터페이스는 함수 시그니처가 아닌 유효 표현식(expression)으로 이루어진다.

다음은 앞서 다루었던 DoProcessing 템플릿의 조건문이다.

if(w.size() > 10 && w != some_nasty_widget)

 

&&가 논리곱 연산의 의미라고 가정할 때, 위 코드로 인해 T에서 제공될 암시적 인터페이스에는 다음과 같은 제약이 걸린다. (실제로는 연산자 오버로딩의 가능성이 있어 T는 아래 두 가지 제약 중 어떤 것도 만족시킬 필요가 없다.) 

 

1. 정수 값을 반환하고 이름이 size인 함수를 지원해야 한다.

size() 멤버 함수는 수치 타입을 반환할 필요까지는 없다. 또한 operator>의 정의에 필요한 타입도 반환할 필요가 없다.

단지 어떤 X타입의 객체와 int가 함께 호출될 수 있는 operator>가 성립될 수 있도록 X타입의 객체만 반환하면 된다.

operator> 함수가 Y 타입의 매개변수를 받도록 정의되어 있고 X타입에서 Y타입으로의 암시적 변환이 가능하기만 하면 되기 때문이다.

 

2. T 타입의 객체 둘을 비교하는 operator != 함수를 지원해야 한다.

T가 operator!= 함수를 지원해야 한다는 제약도 필수 요구사항은 되지 못한다.

operator!= 함수가 X타입의 객체 하나와 Y타입의 객체 하나를 받아들인다고 할 때, T가 X로 변환될 수 있고, some_nasty_widget 변수가 Y로 변환될 수 있다면 opreator!= 의 호출은 유효한 호출로 간주된다.

 

복잡하고 어렵게 느껴지지만 간단하게 생각하자.

if문의 조건식 부분은 bool 표현식이어야 하므로, 표현식에서 쓰이는 내용들이 정확히 어떤 타입인지 신경쓰지 않아도 된다.

즉, "w.size() > 10 && w != some_nasty_widget" 이라는 코드가 정확히 어떤 값을 내놓든지 상관없이 해당 조건식 부분의 결과 값은 bool과 호환되어야 한다는 것이다. 이 제약이 DoProcessing 함수 템플릿이 타입 매개변수인 T에 대해 요구하는 암시적 인터페이스의 일부이다. 나머지 제약은 복사 생성자, normalize(), swap() 함수에 대한 호출이 T 타입의 객체에 대해서 유효해야 한다는 것이다.

 

클래스에서 제공하는 명시적 인터페이스와 호환되지 않는 방법으로 해당 클래스의 객체를 사용할 수 없듯이, 어떤 템플릿 안에서 어떤 객체를 사용하려고 할 때, 해당 템플릿에서 요구하는 암시적 인터페이스를 그 객체가 지원하지 않으면 컴파일 에러가 발생한다.

이것만은 잊지 말자!

  • 클래스 및 템플릿은 모두 인터페이스와 다형성을 지원한다.
  • 클래스의 경우, 인터페이스는 명시적이며 함수의 시그니처를 중심으로 구성되어 있다. 다형성은 프로그램 실행 중에 가상 함수를 통해 나타난다.
  • 템플릿 매개변수의 경우, 인터페이스는 암시적이며 유효 표현식에 기반을 두어 구성된다. 다형성은 컴파일 중에 템플릿 인스턴스화와 함수 오버로딩 모호성 해결을 통해 나타난다.