일반 함수의 정의 중복
이전 글에서 말한 것과 같이, 여러 파일에서 다음과 같이, 동일한 함수를 정의하게 되면, 프로그램 내에는 하나의 정의만 존재해야 된다는 ODR( one definition rule )을 어기게 됩니다.
[C++] 클래스 같은 타입( type )의 정의 중복
타입( type )의 정의 중복이전 글에선, 헤더 가드( header guard )를 설명하면서, 함수의 정의가 있는 헤더 파일을 다른 두 cpp 파일에서 포함하는 경우, 정의의 중복 문제가 발생한다는 것을 얘기했습
codingbonfire.tistory.com
// module.cpp ----------------------------------------
int add( int a, int b){ // 함수 정의
return a + b;
}
int Compute( int a, int b){
return add( a, b);
}
// main.cpp ------------------------------------------
#include <iostream>
int add( int a, int b){ // 함수 정의
return a + b;
}
int main(){
int ret = add( 3, 5 );
std::cout << "result: " << ret << '\n';
}
▼출력
multiple definition of `add(int, int)';
● 이 문제를 해결하는 방법은 두 가지가 있는데, 하나는 함수의 전방 선언( forward declaration )을 사용하는 것입니다.
[C++] 식별자를 알려주는 전방 선언( forward declaration )
전방 선언( forward declaration )식별자( identifier )란 변수, 함수, 타입 등의 이름을 말합니다.int a = 0; // a: 변수 식별자int add( int, int ); // add: 함수 식별자class CSomething; // CSomething: 타입 식별자 이러한
codingbonfire.tistory.com
// module.cpp ----------------------------------------
int add( int a, int b){ // 함수 정의
return a + b;
}
int Compute( int a, int b){
return add( a, b);
}
// main.cpp ------------------------------------------
#include <iostream>
int add( int a, int b); // 전방 선언
int main(){
int ret = add( 3, 5 );
std::cout << "result: " << ret << '\n';
}
▼출력
result: 8
위의 전방 선언의 역할은, 컴파일 시에 컴파일러에게 add 함수의 정의가 프로그램 어딘가에 존재한다는 것을 알려주는 것입니다. ( 컴파일러는 이것으로 만족합니다. )
그리고, 프로그램 작성의 다음 단계인 링크 단계에서는, 링커( linker )가 이 add 식별자와 실제 add 함수의 정의를 연결함으로써, 프로그램이 작동할 때, add 함수의 코드가 실행되는 것입니다.
여기서 주목할 것은, add 함수의 정의는 "module1.cpp" 파일에만 존재한다는 것입니다.
● 두 번째 방법은, inline 함수를 사용하는 것입니다.
[C++] inline 함수에 대한 이해
함수 호출과 inline 확장( expansion )아래의 예문은 main 함수에서 간단한 함수를 호출하는 코드를 보여줍니다.#include int add( int a, int b){ return a + b;}int main(){ int val = add( 3, 5); // 함수 호출 std::cout 그런
codingbonfire.tistory.com
// module.cpp ----------------------------------------
inline int add( int a, int b){ // inline 함수 정의
return a + b;
}
int Compute( int a, int b){
return add( a, b);
}
// main.cpp ------------------------------------------
#include <iostream>
inline int add( int a, int b){ // inline 함수 정의
return a + b;
}
int main(){
int ret = add( 3, 5 );
std::cout << "result: " << ret << '\n';
}
▼출력
result: 8
inline add 함수는 "module.cpp"와 "main.cpp" 두 파일에 모두 정의되어 있지만, 아무런 오류도 발생하지 않습니다.
이것은 링커( linker)가 두 번 중복된 add 함수의 정의를 하나만 남기고 제거해 버리기 때문입니다.
즉, 이 경우도 ODR( one definition rule )을 어기지 않는다는 거죠.
그럼, 템플릿 함수( template function )는 이러한 문제를 어떤 식으로 해결하는 걸까요?
실제로는 눈에 보이지도 않는데 말이죠.
템플릿 함수의 중복 정의
다음은 "module.cpp" 파일 내에서 함수 템플릿( function template )을 사용하여, 템플릿 함수를 인스턴스화( instantiation )하는 코드를 보여줍니다.
그전에 용어를 정리하자면, 아래의 템플릿 코드를 함수 템플릿( function template )이라고 부르고, 이 템플릿으로부터 컴파일러가 만들어내는 함수를 템플릿 함수( template function )라고 합니다.
// module.cpp ----------------------------------------
#include "template.h"
template< typename T > // 함수 템플릿
T add( T a, T b){
return a + b;
}
double ComputeDouble( double a, double b){
return add( a, b);
}
컴파일러가 컴파일 시에 ComputeDouble 함수 안의 add 코드를 만나게 되면, "module.cpp" 파일 안에 다음과 같은 템플릿 함수를 만들어 냅니다.
template<> // 템플릿 함수 ( 실제로는 보이지 않습니다 )
double add<double>( double a, double b){
return a + b;
}
그리고, ComputeDouble 함수는 이 템플릿 함수를 호출하게 되는 것입니다.
그런데, 이러한 함수 템플릿을 "main.cpp" 파일에서도 사용하려면 어떻게 해야 될까요?
먼저 떠올릴 수 있는 방법은 전방 선언( forward declaration )을 하는 것입니다.
// main.cpp ------------------------------------------
#include <iostream>
template< typename T > // 함수 템플릿 전방 선언
T add( T a, T b);
int main(){
int ret = add( 3, 5 );
std::cout << "result: " << ret << '\n';
}
▼출력
undefined reference to `int add<int>(int, int)'
하지만, 위의 결과에서 볼 수 있듯이, 링크 오류를 만나게 됩니다.
왜냐하면, 템플릿 함수( template function )는 컴파일 시에 만들어지는데, 위와 같은 전방 선언만으로는 함수를 생성해 낼 수 없기 때문입니다.
즉, 컴파일 시에 컴파일러는 함수 템플릿의 전체 코드를 알아야 된다는 얘기가 됩니다.
따라서, "main.cpp" 파일도 다음과 같이 함수 템플릿( function template )을 정의해야 됩니다.
// main.cpp ------------------------------------------
#include <iostream>
template< typename T > // 함수 템플릿
T add( T a, T b){
return a + b;
}
int main(){
int ret = add( 3, 5 );
std::cout << "result: " << ret << '\n';
}
여기서, 의문의 생길 것입니다.
이렇게 되면, 위의 함수 템플릿( function template )의 정의를 "main.cpp"와 "module.cpp" 파일 모두 갖게 되므로 중복되는 것 아닌가 하는 것이죠.
C++은 이 문제를 class 같은 타입( type )과 같은 방식으로 처리했습니다.
템플릿을 ODR( one definition rule )의 예외로 둔 것이죠. ( 그리고, 이를 위해서 중복된 정의는 링커에 의해 제거됩니다. )
그래서, 모든 파일에서 위와 같은 함수 템플릿을 정의해도 문제가 발생하지 않습니다.
▼출력
result: 8
아직, 한 가지가 더 남았습니다.
함수 템플릿은 템플릿 타입이라 면제라고 하지만, 이 템플릿으로부터 만들어지는 템플릿 함수( template function )는 어떻게 될까 하는 것입니다.
// module.cpp ----------------------------------------
#include "template.h"
template< typename T > // 함수 템플릿
T add( T a, T b){
return a + b;
}
int ComputeInt( int a, int b){
return add( a, b); // 컴파일 시 add<int> 함수 생성
}
// main.cpp ------------------------------------------
#include <iostream>
template< typename T > // 함수 템플릿
T add( T a, T b){
return a + b;
}
int main(){
int ret = add( 3, 5 ); // 컴파일 시 add<int> 함수 생성
std::cout << "result: " << ret << '\n';
}
컴파일 시, ComputeInt와 main에서 모두 다음과 같은 add <int> 템플릿 함수를 생성합니다. ( 물론 눈에 보이지 않습니다 )
template<> // 컴파일러가 만드는 템플릿 함수
int add< int >( int a, int b ){
return a + b;
}
이것은 분명히 정의의 중복입니다.
그렇지만, 이번엔 C++은 템플릿 함수( template function )를 암시적으로 inline 함수로 취급하는 것으로 문제를 해결합니다.
위에서 말했듯이, inline 함수는 정의의 중복을 허가합니다.
따라서, 템플릿 함수에 관련된 중복의 문제는 해결되었습니다.
이제, 위의 함수 템플릿을 원하는 모든 파일들을 위해서 헤더 파일에 정의하는 것이 편리합니다.
이것은 템플릿의 코드가 중복되는 것을 막을 뿐만 아니라, 수정 시에도 관리를 편리하게 만들어 줍니다.
그리고, 중복을 막기 위해선 링커( linker )가 중복되는 정의를 삭제해야 되는데, 파일마다 다른 정의를 가진 함수 템플릿이 있다면, 프로그램이 정의되지 않은 동작을 하게 됩니다.
// template.h ----------------------------------------
#ifndef TEMPLATE_H // header guard
#define TEMPLATE_H
template< typename T > // 함수 템플릿
T add( T a, T b){
return a + b;
}
#endif
// module.cpp ----------------------------------------
#include "template.h"
int ComputeInt( int a, int b){
return add( a, b);
}
// main.cpp ------------------------------------------
#include <iostream>
#include "template.h"
int main(){
int ret = add( 3, 5 );
std::cout << "result: " << ret << '\n';
}
▼출력
result: 8
그리고, 헤더 파일에 헤더 가드( header guard )를 설치해, 한 파일 내에서 정의가 중복되는 것을 방지하는 것이 바람직합니다.
정리
- 함수 템플릿( function template )은 ODR( one definition rule )에 면제입니다.
- 템플릿 함수( template function )는 암시적으로 inline 함수입니다.
- 함수 템플릿은 헤더 파일에 정의하는 것이 편리합니다.