ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Template] 템플릿 구체화
    개발/C·C++ 2021. 7. 5. 04:19

    STL 컨테이너를 보면 선언과 정의가 분리되어 있지 않습니다. 클래스는 선언부와 정의부를 나누는 경우가 조금 더 일반적일 텐데요. 일반화 프로그래밍이라는 템플릿 라이브리리는 일반적인 방법을 따르지 않는 것처럼 보입니다. 라이브러리를 만드는 사람들이 뭘 몰라서 그런 것은 아닐 테니까, 그럴 만한 이유가 있을 겁니다. 그 이유를 파악해 봅시다.

    #include <vector>
    int main()
    {
    	std::vector<int> vec;
    	for (int i = 0; i < 5; ++i)
    		vec.push_back(i);
    }

    간단한 벡터 예제입니다. <> 연산자를 통해 int형 데이터를 담을 수 있는 컨테이너를 만들어 반복문을 이용해 0부터 4까지 넣었습니다. std::vector<int>을 명시적 구체화라고 합니다. 함수 템플릿에 넣는 인수를 통해 타입을 유추할 수 있는 경우에는, 꺽쇠에 타입을 넣어주지 않아도 됩니다. 이는 암시적 구체화라고 합니다.

     

    위의 코드에서는 벡터 객체가 만들어질 때 저장할 타입이 정해져야 하므로 암시적 구체화를 할 수 없습니다. 구체화를 해야 비로소 코드가 만들어집니다. 함수 템플릿은 함수가 아니며 클래스 템플릿은 클래스가 아닙니다. 이것만으로는 코드가, 기계어가 생성되지 않습니다. 구체화는 필수입니다. 이를 알아보기 위해 swap 함수 템플릿을 선언과 정의를 분리하고 main 함수에서 호출해보겠습니다.

    // swap.h
    #pragma once
    template<typename T>
    void swap(T& a, T& b);
    
    
    // swap.cpp
    #include "swap.h"
    template<typename T>
    void swap(T& a, T& b)
    {
    	T temp = a;
    	a = b;
    	b = temp;
    }
    
    
    // main.cpp
    #include "swap.h"
    #include <iostream>
    using std::cout;
    using std::endl;
    int main()
    {
    	int a = 10, b = 20;
    	swap<int>(a, b);
    	cout << a << ", " << b << endl;
    }

    에러가 발생합니다.

    이게 무슨 소리일까요. 분명히 cpp 파일에 정의를 해줬는데 함수 본체를 찾을 수 없다니요. 앞서 말했던 것처럼 함수 템플릿은 함수가 아닙니다. 함수 템플릿은 함수를 찍어내는 형틀일 뿐입니다. 이름부터 템플릿이죠. cpp 파일에 있는 코드는 실제로 만들어지지 않았으므로 메모리에 올라가지 않습니다. 어디에도 존재하지 않아요. swap 함수를 호출할 때 <int>를 붙였지만 구체화되지 않은 것이죠. stld의 벡터와 다르게 동작하는 것 같습니다. 이처럼 파일이 분리되어 있을 때 구체화 문제를 해결하기 위해서는, 따로 구체화를 해야 합니다. cpp 파일에 int에 대한 구체화 코드를 추가했습니다.

    // swap.h
    //.. 생략
    
    
    // swap.cpp
    #include "swap.h"
    template<typename T>
    void swap(T& a, T& b)
    {
    	T temp = a;
    	a = b;
    	b = temp;
    }
    template void swap(int&, int&);
    
    
    // main.cpp
    // ..생략
    int main()
    {
    	int a = 10, b = 20;
    	swap<int>(a, b);
    	cout << a << ", " << b << endl;
    }

    이제는 오류 없이 swap 함수가 작동합니다. float에 대한 것은 어떻게 처리해야 할까요. int를 정의해뒀으니까 값 손실이 일어나더라도 float 인수를 넣어도 되지 않을까, 생각할 수 있습니다. 하지만 안 됩니다. 기호를 찾을 수 없다는 오류가 다시 발생합니다. int형에서 했던 것처럼 template void swap(float&, float&) 코드를 추가해 구체화를 해줘야 합니다.

     

    뭔가 좀 이상합니다. 템플릿을 쓰는 이유는 일반화 프로그래밍이라고 하는, 인수 타입에 구애받지 않고 인터페이스를 작성하는 것이 주된 목적인데, 이런 방법으로 구체화를 하는 것은 일반화 프로그래밍의 장점을 살리지 못하는 것 같습니다. 해결책은 간단합니다. 처음에 말했던 것처럼, 선언부와 정의부를 분리하지 않고 헤더에 정의부까지 모두 작성하면 됩니다.

    // swap.h
    #pragma once
    template<typename T>
    void swap(T& a, T& b)
    {
    	T temp = a;
    	a = b;
    	b = temp;
    }
    
    
    // main.cpp
    #include "swap.h"
    using std::cout;
    using std::endl;
    int main()
    {
    	int a = 10, b = 20;
    	swap<int>(a, b);
    	cout << a << ", " << b << endl;
    }

     

    클래스도 마찬가지입니다. 다음의 클래스 템플릿으로 확인해봅시다.

    // Test.h
    #pragma once
    template<typename T>
    class Test
    {
    public:
    	Test() = default;
    	Test(T data);
    	~Test() = default;
    	T& getData();
    private:
    	T _data;
    };
    
    
    // Test.cpp
    #include "Test.h"
    template<typename T>
    Test<T>::Test(T data)
    	:_data(data) {}    
    template<typename T>
    T& Test<T>::getData()
    {
    	return _data;
    }
    template Test<int>;
    
    
    // main.cpp
    #include "Test.h"
    #include <iostream>
    using std::cout;
    using std::endl;
    int main()
    {
    	Test<int> test(10);
    	cout << test.getData() << endl;
    }

    위 코드는 오류가 없지만 Test.cpp에서 template Test<int> 코드를 삭제하면, 구체화가 일어나지 않기 때문에 다음과 같은 오류가 발생합니다.

    사용하게 될 타입의 종류가 확실하게 정해져 있다면 cpp 파일에 구체화 코드를 넣는 방법도 활용해볼 수 있을 것입니다. 하지만 해당 기능을 라이브러리로 만들어야 한다든가 혹은 모든 타입에 대해서 기능을 지원해야 한다면, 앞서 함수 템플릿에서 확인했던 것처럼 클래스 선언부에서 정의까지 작성해야 할 것입니다.

    '개발 > C·C++' 카테고리의 다른 글

    condition_variable::wait  (0) 2021.07.26
    Default arguments  (0) 2021.07.05
    [Priority queue] 간단한 참고  (0) 2021.07.03
    Guaranteed Copy Elison  (0) 2021.06.27
    InterlockedExchange, InterlockedCompareExchange  (0) 2021.06.10

    댓글

Designed by Tistory.