안녕하세요.
C++로 프로그래밍하다 보면, 비슷한 로직인데 처리하는 데이터 타입만 다른 함수를 여러 개 만들어야 할 때가 있습니다.
예를 들어, 두 개의 정수를 더하는 함수 add_int(int a, int b)와 두 개의 실수를 더하는 함수 add_float(float a, float b) 코드로 나눠져 있다면, 코드가 중복되고 관리하기 번거로울 것입니다.
C++은 함수 템플릿(Function Template)이라는 함수 템플릿을 사용하면, 타입에 구애받지 않는 함수 "틀"을 만들어 놓고, 컴파일 시점에 실제 필요한 타입의 함수를 자동으로 생성하게 할 수 있습니다.
이미 알게 모르게 사용하고 있을 수 있지만, 기초 방법과 연계해서 +1 지식을 알아보도록 하겠습니다.
1. 함수 템플릿이란?
함수 템플릿은 함수를 찍어내는 틀(Template)입니다. 특정 타입에 종속되지 않고, 일반적인 타입(T와 같은 템플릿 매개변수 사용)으로 함수의 로직을 정의합니다.
template <typename T> // T라는 이름의 타입 템플릿 매개변수를 선언
T add(T _left, T _right) { // 반환 타입과 매개변수 타입으로 T를 사용
return _left + _right;
}
위 코드에서 template <typename T>는 "이제부터 템플릿을 정의할 것이고, T라는 이름의 타입 매개변수를 사용할 거야"라고 컴파일러에게 알려주는 선언입니다.
typename 대신 class 키워드를 사용해도 동일하게 동작합니다.
T는 함수가 호출될 때 실제 사용될 타입(예: int, float, double 등)으로 대체됩니다.
2. 함수 템플릿의 동작 형태 (컴파일 타임 함수 생성)
함수 템플릿 자체는 실제 함수가 아닙니다. 우리가 템플릿 함수를 특정 타입으로 호출하면, 컴파일러는 그 호출 시점에 해당 타입에 맞는 실제 함수 코드를 생성합니다.
이 과정을 템플릿 인스턴스화(Template Instantiation)라고 부릅니다.
예를 들어, 위에서 만든 add 템플릿 함수를 int 타입과 float 타입으로 호출하면
int main() {
add(3, 5); // 컴파일러가 T를 int로 추론 -> int add(int, int) 함수 생성
add(3.3f, 5.32f); // 컴파일러가 T를 float로 추론 -> float add(float, float) 함수 생성
return 0;
}
컴파일러가 add(3, 5)는 T를 int로 추론해서 int add(int, int); 함수로 add(3.3f, 5.32f); 는 float add(float, float); 함수로 내부적으로 두 개의 함수를 만듭니다.
// 컴파일러가 생성한 함수 (실제 코드에 보이는 것은 아님)
int add_int_version(int _left, int _right) {
return _left + _right;
}
float add_float_version(float _left, float _right) {
return _left + _right;
}
이처럼 함수 템플릿은 "함수를 만들어내는 함수", 즉 메타 함수(Meta Function)라고 생각할 수도 있습니다. 하나의 템플릿 정의로 여러 타입의 함수 버전을 효율적으로 관리할 수 있게 해 줍니다.
3. 탬플릿 함수를 위한 예제 코드
위의 내용과 추가적인 이해를 위해서 템플릿 함수 동작을 위한 코드를 작성해 봤습니다.
#include <stdio.h>
//add 템플릿 함수 정의
template <typename T>
T add(T _left, T _right) {
printf("--- add 함수 호출 ---\n");
printf("Function Signature: %s\n", __FUNCSIG__);
printf("Decorated Name: %s\n", __FUNCDNAME__);
return _left + _right;
}
// 2) getMax 템플릿 함수 정의
template <typename DATA_TYPE>
DATA_TYPE getMax(DATA_TYPE _a, DATA_TYPE _b) {
printf("--- getMax 함수 호출 ---\n");
printf("Decorated Name: %s\n", __FUNCDNAME__);
return _a > _b ? _a : _b;
}
int main() {
printf("결과: int add : %d \n\n", add(3, 5)); // 1) int 타입으로 add 호출
printf("결과: float add : %5.2f \n\n", add(3.3f, 5.32f)); // 2) float 타입으로 add 호출
int i = 3;
int j = 5;
getMax(i, j); // 3) int 타입으로 getMax 호출 (반환값을 사용하지 않음)
return 0;
}
< 코드 설명 >
1) add(3, 5) 호출:
컴파일러는 인자 3과 5를 보고 T를 int로 추론(Deduction) 합니다.
이를 암시적 인스턴스화(Implicit Instantiation)라고 합니다. int add(int, int) 버전의 함수 코드가 생성됩니다.
__FUNCSIG__와 __FUNCDNAME__을 통해 컴파일러가 생성한 함수의 시그니처와 내부 이름을 확인할 수 있습니다. (출력 결과는 컴파일러마다 다릅니다.)
2) add(3.3f, 5.32f) 호출:
컴파일러는 인자 3.3f와 5.32f를 보고 T를 float로 추론합니다. float add(float, float) 버전의 함수 코드가 생성됩니다.
3) getMax(i, j) 호출:
컴파일러는 int 타입 변수 i와 j를 보고 DATA_TYPE을 int로 추론합니다. int getMax(int, int) 버전의 함수 코드가 생성됩니다. __FUNCDNAME__을 통해 내부 이름을 확인합니다.
getMax<int>(i, j); // T를 int로 명시적으로 지정
함수 이름 뒤에 <타입>을 붙여주면 됩니다. 위 예시에서는 컴파일러가 어차피 int로 추론 가능하므로 굳이 명시적으로 적을 필요는 없지만, 더 복잡한 상황이나 가독성을 위해 사용될 수 있습니다.
4. 코드 실행 결과
add()와 getMax()의 단순 출력은 설명할 것이 없지만, Name Mangling (Name Decoration)이라는 것에 설명이 조금 필요할 것 같습니다.
위의 소스 코드를 보면 __FUNCSIG__ 와 __FUNCDNAME__ 같이 미리 지정된 매크로를 사용하고 있습니다.
__FUNCSIG__ : 함수 이름, 반환 타입, 매개변수 타입을 포함한 전체 함수 시그니처 문자열을 보여줍니다. (MSVC용)
__FUNCDNAME__ : 컴파일러가 내부적으로 사용하는 장식된(Mangled) 이름을 보여줍니다. 이 이름은 컴파일러마다 규칙이 다릅니다. (MSVC용 이므로 GCC/Clang에서는 __PRETTY_FUNCTION__ 등이 유사한 기능을 합니다.)
C++에서는 함수 오버로딩(Overloading)처럼 같은 이름의 함수를 여러 개 정의할 수 있습니다. 또한, 함수 템플릿으로부터 여러 버전의 함수(add<int>, add<float>)가 생성됩니다.
컴파일러와 링커는 이들을 구분해야 하므로, 함수 이름에 매개변수 타입 정보 등을 덧붙여 고유한 내부 이름으로 만듭니다. 이것이 컴파일러가 같은 이름의 함수들을 구별하는 핵심 메커니즘입니다
추가로, C와 C++ 호환성문제가 생길 수 있는 이유 중 하나가 바로 이 때문입니다. C 언어는 이름 데커레이션 기능이 없습니다. 따라서 C++ 코드에서 C 라이브러리 함수를 호출하거나, C 코드에서 C++ 함수를 호출하려면 이름 데커레이션 방식의 차이 때문에 문제가 생길 수 있습니다.
이때 extern "C"를 사용하여 C++ 컴파일러에게 해당 함수는 C 언어의 규칙을 따르도록 명시해주어야 합니다.
감사합니다.
<참고 사이트>
1. Name Mangling (Name Decoration)
'Programming > C, C++' 카테고리의 다른 글
[C++] 타입 캐스팅 (static_cast, dynamic_cast) 대하여 (0) | 2025.05.10 |
---|---|
[C++] 연산자 오버로딩(Operator Overloading)에 대해 좀 더 알아보기 (0) | 2025.04.29 |
[C++] using 정의와 추가적인 기능 알아보기 (0) | 2025.04.25 |
[C++] namespace 사용법 읽을거리 (0) | 2025.04.23 |
[C++] 동적 바인딩과 가상 함수에 대한 짧게 읽을거리 (1) | 2025.04.17 |