아래는 두 수를 비교하는 일반 함수이다. int, float, double 자료형에 따라서 구현을 한다면 아래와 같을 것이다.
int max(int a, int b)
{
// b < a 라면 a를 반환하고 아니라면 b를 반환한다.
return b > a ? a : b;
}
float max(float a, float b)
{
// b < a 라면 a를 반환하고 아니라면 b를 반환한다.
return b > a ? a : b;
}
double max(double a, double b)
{
// b < a 라면 a를 반환하고 아니라면 b를 반환한다.
return b > a ? a : b;
}
template 사용하면 어느 자료형에 상관없이 컴파일러가 Runtime(실행 시간)에 유추하여 template 인스턴스화한다. 아래는 template 메서드이다.
template<typename T>
T max (T a, T b)
{
// b < a 라면 a를 반환하고 아니라면 b를 반환한다.
return b > a ? a : b;
}
역사적인 이유로 형식 파라미터를 정의할 때 typenmae 대신 class 사용할 수도 있다. typename이라는 키워드는 C++ 98 표준을 만드는 중 상당히 늦게 도입됐다. 그전에는 형식 파라미터를 키워드 class로 도입해야 했기 때문에 여전히 typename 대신 class를 사용할 수 있다.
그래서 template max()는 다음처럼 정의해도 된다.
template<class T>
T max(T a, T b)
{
return b > a ? a : b;
}
의미상 두 함수는 다를 게 전혀 없다. 따라서 여기서 class를 사용하더라도 템플릿 인자로 어떠한 형식이든 사용할 수 있다. 하지만 class를 사용하면 오해할 수 있으므로(T로 클래스 형만을 쓸 수 있는 게 아닌데도) typename을 사용하는 편이 좋다. 클래스 형 선언과 달리 typename 대신 키워드 struct는 쓸 수 없다.
#include "max1.hpp"
#include <iostream>
#include <string>
int main() {
int i = 42;
std::cout << "max(7, i):" << ::max(7, i) << '\n';
double f1 = 3.4;
double f2 = -6.7;
std::cout << "max(f1, f2):" << ::max(f1, f2) << '\n';
return 0;
}
max() 템플릿을 호출할 때마다 ::으로 한정했다는 것을 눈여겨보자. 이렇게 표시하면 전역 네임스페이스에 우리의 max() 템플릿을 찾는다. 표준 라이브러리에도 std::max() 템플릿이 있기 때문에 ::로 한정하지 않으면 (오버 로딩 해석 & C++의 룩업법칙) 에서 모호하게 될 수도 있다.
일반적으로 템플릿이 어떠한 형식이라도 다룰 수 있는 하나의 실체로 컴파일 되지는 않는다. 대신 템플릿이 사용될 때마다 템플릿에서부터 각형식에 맞는 실체를 만든다. 실제로는 아래와 같이 동작한다.
int i = 45;
... max(7, i) ...
위 코드는 실제로 호출된 함수의 의미는 아래와 같을 것이다.
int max(int a, int b)
{
// b < a 라면 a를 반환하고 아니라면 b를 반환한다.
return b > a ? a : b;
}
템플릿 파라미터를 실제 형식으로 바꾸는 작업은 인스턴스화라고 한다. 이를 통해 템플릿의 인스턴스가 생성된다. 함수 템플릿을 사용하기만 해도 인스턴스화가 일어 난다. 다른 코드가 max()를 호출할 때에도 개별로 구현된 것처럼 double, string을 위한 max 템플릿이 인스턴스화 된다.
double max(double, double);
std::string max(std::string, std::string);
함수 템플릿 내에서 사용된 모든 연산자를 지원하지 않는 형식에 대해 템플릿을 인스턴스화하면 컴파일 오류가 발생한다.
std::complex<float> c1, c2; // 연산자(operator) <를 제공하지 않는다.
...
::max(c1, c2); // 컴파일 시 오류 발생
1. 정의 시간에 인스턴스화 없이 템플릿 파라미터는 무시한 채 템플릿 자체의 문법이 정확한지 검사한다. 이때 다음과 같은 것을 검사한다.
- 세미콜론(;)이 빠졌다든가 하는 문법 오류
- 템플릿 파라미터에 종속되지 않는 이름(형식 이름, 함수 이름 등등)이 알려지지 않음
- 템플릿 파라미터에 종속되지 않는 정적 단언문
2. 인스턴스화되는 시점에서 모든 코드가 유효한지 확인하기 위해 템플릿 코드를 검사한다. 템플릿 파라미터에 종속된 부분은 두 번 검사한다.
template<typename T>
void foo(T t)
{
undeclared(); // undeclared()가 알려지 않았다면 첫 번째 단계 컴파일 오류
undeclared(T); // undeclared(T)가 알려지지 않았다면 두 번째 단계 컴파일 오류
static_assert(sizeof(int) > 10, "int too small"); //sizeof(int)<=10 라면 항상 실패
static_assert(sizeof(T) > 10, "int too small"); // 크기가 10보다 작거나 같은 T로 인스턴스화 됐다면 실패
}
이름들을 두 번 검사한다는 점에서 두 단계 룩업lookup이라고 한다. 일부 컴파일러는 첫 번째 단계에서 전체 검사를 하지 않는다. 템플릿이 한 번도 인스턴스화되지 않으면 일반적인 문제도 알아내지 못할 수 있다.
이렇게 두 번 컴파일되기 때문에 실제로 템플릿을 처리할 때 큰 걸림돌이 있다. 함수 템플릿을 사용해 인스턴스화를 발생시키려면 컴파일러가 템플릿의 정의를 알아야 한다는 것이다.
일반 함수에서는 컴파일과 링크가 분리돼 함수의 선언만 알아도 그 함수를 사용하는 코드를 컴파일할 수 있었지만, 템플릿을 사용하면 이 둘을 쉽게 분리할 수 없다. 해당 문제를 해결하기 위한 가장 간단한 방법은 모든 템플릿은 헤더 파일에 구현한다.
C++ 메모리 관리 (0) | 2021.04.29 |
---|---|
template method 기본 인자에 대한 형식 연역 (0) | 2021.04.29 |
전방 선언 Forward Declaration (0) | 2020.09.25 |
타입추론 (0) | 2020.06.22 |
Const 레퍼런스 전달 방식 (0) | 2020.06.22 |
댓글 영역