c++ - source - template function specialization in cpp file




템플릿을 헤더 파일에만 구현할 수있는 이유는 무엇입니까? (10)

C ++ 표준 라이브러리 에서 인용 : 튜토리얼 및 핸드북 :

템플릿을 사용하는 유일한 방법은 인라인 함수를 사용하여 헤더 파일에 템플릿을 구현하는 것입니다.

왜 이런거야?

(명확화 : 헤더 파일 만이 유일한 휴대용 솔루션은 아니지만 가장 편리한 휴대용 솔루션입니다.)


구현을 헤더 파일에 넣을 필요는 없습니다 .이 대답의 끝 부분에있는 대체 솔루션을 참조하십시오.

어쨌든 코드가 실패하는 이유는 템플릿을 인스턴스화 할 때 컴파일러가 주어진 템플릿 인수로 새로운 클래스를 생성한다는 것입니다. 예 :

template<typename T>
struct Foo
{
    T bar;
    void doSomething(T param) {/* do stuff using T */}
};

// somewhere in a .cpp
Foo<int> f; 

이 줄을 읽을 때 컴파일러는 다음과 같은 새로운 클래스를 만듭니다 ( FooInt 라고 FooInt ).

struct FooInt
{
    int bar;
    void doSomething(int param) {/* do stuff using int */}
}

따라서 컴파일러는 메서드의 구현에 액세스하여 템플릿 인수 (이 경우 int )로 인스턴스를 생성해야합니다. 이러한 구현이 헤더에 없으면 액세스 할 수 없으므로 컴파일러는 템플릿을 인스턴스화 할 수 없습니다.

일반적인 해결책은 헤더 파일에 템플릿 선언을 작성한 다음 구현 파일 (예 : .tpp)에 클래스를 구현하고이 구현 파일을 헤더의 끝에 포함시키는 것입니다.

// Foo.h
template <typename T>
struct Foo
{
    void doSomething(T param);
};

#include "Foo.tpp"

// Foo.tpp
template <typename T>
void Foo<T>::doSomething(T param)
{
    //implementation
}

이렇게하면 구현은 여전히 ​​선언과 분리되지만 컴파일러에서 액세스 할 수 있습니다.

또 다른 해결책은 구현을 분리 된 상태로 유지하고 필요한 모든 템플릿 인스턴스를 명시 적으로 인스턴스화하는 것입니다.

// Foo.h

// no implementation
template <typename T> struct Foo { ... };

//----------------------------------------    
// Foo.cpp

// implementation of Foo's methods

// explicit instantiations
template class Foo<int>;
template class Foo<float>;
// You will only be able to use Foo with int or float

제 설명이 명확하지 않은 경우이 주제에 대한 C ++ 수퍼 FAQ를 살펴보십시오.


그것은 별도 컴파일에 대한 요구와 템플릿이 인스턴스화 스타일의 다형성이기 때문입니다.

설명을 좀 더 구체적으로 할 수 있습니다. 다음 파일이 있다고 가정 해보십시오.

  • foo.h
    • class MyClass<T> 의 인터페이스를 선언한다.
  • foo.cpp
    • class MyClass<T> 의 구현을 정의합니다.
  • bar.cpp
    • MyClass<int> 사용합니다. MyClass<int>

별도의 컴파일은 bar.cpp 와는 별도로 foo.cpp 를 컴파일 할 수 있어야한다는 것을 의미합니다. 컴파일러는 각 컴파일 유닛의 모든 분석, 최적화 및 코드 생성 작업을 완전히 독립적으로 수행합니다. 우리는 전체 프로그램 분석을 할 필요가 없습니다. 한 번에 전체 프로그램을 처리해야하는 링커 일 뿐이며 링커의 작업은 훨씬 쉬워집니다.

bar.cppfoo.cpp를 컴파일 할 때도 존재할 필요가 없지만 foo 를 다시 컴파일 할 필요없이 bar.o 와 함께 사용했던 foo.o 를 연결할 수 있어야합니다. .cpp . foo.cpp 는 동적 라이브러리로 컴파일 될 수도 있고 foo.cpp 없이 다른 곳으로 배포 될 수도 있으며 foo.cpp를 작성한 후 수년 간 작성하는 코드로 링크 될 수도 있습니다.

"인스턴스화 스타일의 다형성"은 MyClass<T> 템플릿이 실제로 T 값을 처리 할 수있는 코드로 컴파일 할 수있는 제네릭 클래스가 아니라는 것을 의미합니다. 그것은 복싱과 같은 오버 헤드를 추가하고, 할당 자와 생성자에게 함수 포인터를 전달할 필요가 있습니다. C ++ 템플릿의 목적은 거의 동일한 class MyClass_int , class MyClass_float 등을 작성하지 않고도 끝낼 수 있도록하는 것입니다. 컴파일 된 코드는 주로 각 버전을 별도로 작성한 것과 같습니다. 따라서 템플릿은 말 그대로 템플릿입니다. 클래스 템플릿은 클래스가 아니며 , 우리가 만나는 각각의 T 대한 새로운 클래스를 생성하기위한 레서피입니다. 템플릿을 코드로 컴파일 할 수 없으므로 템플릿을 인스턴스화 한 결과 만 컴파일 할 수 있습니다.

따라서 foo.cpp 가 컴파일 될 때 컴파일러는 bar.cpp 를보고 MyClass<int> 가 필요하다는 것을 알 수 없습니다. MyClass<T> 템플릿을 볼 수는 있지만, 클래스를위한 것이 아닌 템플릿입니다. 그리고 bar.cpp 가 컴파일되면 컴파일러는 MyClass<int> 를 만들어야한다는 것을 알 수 있지만 MyClass<T> ( foo.h 의 인터페이스 만) 템플릿을 볼 수 없으므로 만들 수 없습니다 그것.

foo.cpp 자체에서 MyClass<int> 사용하면 foo.cpp 를 컴파일하는 동안 해당 코드가 생성되므로 bar.ofoo.o에 링크되면 연결될 수 있고 작동합니다. 이 사실을 이용하여 단일 템플릿을 작성하여 .cpp 파일에 유한 템플릿 인스턴스 집합을 구현할 수 있습니다. 그러나 bar.cpp 가 템플릿을 템플릿 으로 사용하고 원하는 형식으로 인스턴스화하는 방법은 없습니다. foo.cpp 의 작성자가 제공 할 것으로 생각하는 템플릿 기반 클래스의 기존 버전 만 사용할 수 있습니다.

템플릿을 컴파일 할 때 컴파일러는 "모든 버전 생성"을 사용해야한다고 생각할 수도 있습니다. 포인터 및 배열과 같은 "유형 수정 자"기능을 사용하면 내장 된 유형으로도 무한한 유형의 숫자가 발생할 수 있으므로 이러한 오버 헤드와 극단적 인 어려움을 피할 수 있습니다. 프로그램을 확장하면 어떻게됩니까? 추가하여:

  • baz.cpp
    • class BazPrivate 선언하고 구현 class BazPrivate , MyClass<BazPrivate>

이 방법이 효과가있을 수있는 방법은 없습니다.

  1. 프로그램에서 다른 파일을 변경할 때마다 foo.cpp 를 재 컴파일해야합니다. MyClass<T> 의 새로운 소설 인스턴스가 추가 된 경우에 대비해야합니다.
  2. baz.cppbaz.cpp를 컴파일하는 동안 컴파일러가 MyClass<BazPrivate> 생성 할 수 있도록 MyClass<BazPrivate>MyClass<T> 의 전체 템플릿이 포함되어 있어야 합니다 (헤더 포함 가능).

아무도 (1)을 좋아하지 않습니다. 왜냐하면 전체 프로그램 분석 컴파일 시스템이 영원히 컴파일되기 때문이며, 소스 코드없이 컴파일 된 라이브러리를 배포 할 수 없기 때문입니다. 그래서 (2) 대신에


별도의 구현 방법은 다음과 같습니다.

//inner_foo.h

template <typename T>
struct Foo
{
    void doSomething(T param);
};


//foo.tpp
#include "inner_foo.h"
template <typename T>
void Foo<T>::doSomething(T param)
{
    //implementation
}


//foo.h
#include <foo.tpp>

//main.cpp
#include <foo.h>

inner_foo에는 전달 선언이 있습니다. foo.tpp가 구현되어 inner_foo.h를 포함합니다. 그리고 foo.h는 foo.tpp를 포함하기 위해 단지 한 줄을 가질 것입니다.

컴파일 할 때 foo.h의 내용이 foo.tpp에 복사 된 다음 전체 파일이 foo.h에 복사 된 후 컴파일됩니다. 이 방법은 아무런 제한이 없으며 하나의 파일을 추가하는 대신 이름이 일관됩니다.

정적 분석기가 * .tpp 클래스의 forward 선언을 볼 수 없을 때 코드가 중단되기 때문에이 작업을 수행합니다. 어떤 IDE에 코드를 작성하거나 YouCompleteMe 또는 다른 것을 사용할 때 이것은 성가시다.


실제로 객체 코드로 컴파일하기 전에 템플릿을 컴파일러에서 인스턴스화 해야합니다. 이 인스턴스화는 템플릿 인수가 알려진 경우에만 수행 할 수 있습니다. 이제 a.cpp 에서 정의 된 템플릿 함수가 ah 로 선언되고 a.cpp 에서 사용되는 시나리오를 상상해보십시오. a.cpp 가 컴파일 될 때, 앞으로의 컴파일 b.cpp 가 템플릿의 인스턴스를 필요로한다는 것은 반드시 알려져 있지 않습니다. 더 많은 헤더 및 소스 파일을 보려면 상황이 더 빨리 복잡해질 수 있습니다.

컴파일러를 사용하여 템플릿의 모든 용도를 "앞 당길"수 있다고 주장 할 수는 있지만 재귀 적 또는 복잡한 시나리오를 만드는 것이 어렵지 않을 것이라고 확신 할 수 있습니다. AFAIK, 컴파일러는 그런 미래 지향적 인 일을하지 않습니다. Anton가 지적한 것처럼, 일부 컴파일러는 템플릿 인스턴스화의 명시 적 내보내기 선언을 지원하지만 모든 컴파일러가이를 지원하지는 않습니다 (아직?).


여기서 주목할만한 것을 추가하십시오. 함수 템플릿이 아닌 경우 구현 파일에서 템플릿 클래스의 메서드를 잘 정의 할 수 있습니다.

myQueue.hpp :

template <class T> 
class QueueA {
    int size;
    ...
public:
    template <class T> T dequeue() {
       // implementation here
    }

    bool isEmpty();

    ...
}    

myQueue.cpp :

// implementation of regular methods goes like this:
template <class T> bool QueueA<T>::isEmpty() {
    return this->size == 0;
}


main()
{
    QueueA<char> Q;

    ...
}

여기에 정답이 많이 있지만, 이것을 (완전성을 위해) 추가하고 싶습니다 :

구현 cpp 파일의 맨 아래에 템플리트가 사용될 모든 유형의 명시 적 인스턴스화를 수행하면 링커는 평소대로이를 찾을 수 있습니다.

편집 : 명시 적 템플릿 인스턴스화 예제 추가. 템플릿이 정의되고 모든 멤버 함수가 정의 된 후에 사용됩니다.

template class vector<int>;

이렇게하면 클래스와 그 모든 멤버 함수를 인스턴스화하여 링커가 사용할 수있게합니다. 템플리트 함수에 대해서도 비슷한 구문이 적용되므로 비 멤버 연산자 오버로드가있는 경우 해당 연산자에 대해 동일한 작업을 수행해야 할 수도 있습니다.

위의 예제는 헤더가 완전히 정의 되었기 때문에 꽤 쓸모가 없다. 공통 include 파일 (excompiled header?)이 extern template class vector<int> 하여 다른 모든 (1000?) 파일에서 인스턴스화하지 못하게하는 경우는 예외이다. 그 벡터를 사용합니다.


이것은 컴파일러가 할당을위한 유형을 알아야하기 때문에 정확합니다. 그래서 c / cpp 파일과 달리 헤더 파일이 컴파일되지 않기 때문에 템플릿 클래스, 함수, 열거 형 등이 헤더 파일에 공용으로 구현되거나 라이브러리의 일부 (정적 또는 동적)로 구현되어야합니다. 아르. 컴파일러가 형식을 모르는 경우 컴파일 할 수 없습니다. .Net에서는 모든 객체가 Object 클래스에서 파생되기 때문에 그렇게 할 수 있습니다. 이것은 .Net이 아닙니다.


즉, 템플릿 클래스의 메서드 구현을 정의하는 가장 이식 가능한 방법은 템플릿 클래스 정의 내에 정의하는 것입니다.

template < typename ... >
class MyClass
{

    int myMethod()
    {
       // Not just declaration. Add method implementation here
    }
};

컴파일러는 템플릿 매개 변수에 대해 제공된 / 가져온 매개 변수에 따라 다른 버전의 코드를 인스턴스화해야하므로 헤더에서 템플릿을 사용해야합니다. 템플릿은 코드를 직접 나타내지는 않지만 해당 코드의 여러 버전에 대한 템플릿을 기억하십시오. .cpp 파일에서 템플릿 이외의 함수를 컴파일하면 구체적인 함수 / 클래스를 컴파일하게됩니다. 다른 유형으로 인스턴스화 할 수있는 템플릿의 경우는 아닙니다. 즉, 템플릿 매개 변수를 콘크리트 유형으로 바꿀 때 구체적인 코드가 생성되어야합니다.

별도의 컴파일에 사용되는 export 키워드가있는 기능이 있습니다. export 기능은 C++11 및 AFAIK에서 더 이상 사용되지 않으며 구현 된 컴파일러는 하나뿐입니다. export 을 이용해서는 안됩니다. 별도의 컴파일은 C++ 이나 C++11 에서는 가능하지 않지만 C++ 에서 가능할 수도 있습니다. C++17 개념이 만들어지면 별도의 컴파일 방법이있을 수 있습니다.

개별 컴파일을 수행하려면 별도의 템플릿 본문 확인이 가능해야합니다. 컨셉으로 해결이 가능할 것으로 보인다. 최근 표준위원회 회의에서 발표 된이 paper 살펴보십시오. 나는 이것이 사용자 코드에서 템플릿 코드의 코드를 인스턴스화해야하기 때문에 이것이 유일한 요구 사항은 아니라고 생각합니다.

템플릿에 대한 별도의 컴파일 문제 나는 모듈로의 마이그레이션과 관련하여 현재 문제가되고있는 것으로 생각됩니다.


표준 C ++에는 이러한 요구 사항이 없지만 일부 컴파일러에서는 모든 함수 및 클래스 템플릿을 사용되는 모든 변환 단위에서 사용할 수 있어야합니다. 결과적으로, 이러한 컴파일러의 경우 템플릿 함수 본문을 헤더 파일에서 사용할 수 있어야합니다. 반복하려면 : 즉, 해당 컴파일러는 .cpp 파일과 같은 비 - 헤더 파일에 정의되도록 허용하지 않습니다.

이 문제를 완화시켜야하는 내보내기 키워드가 있지만 이식성이 거의 없습니다.





c++-faq