C ++ 표준 라이브러리 에서 인용 : 튜토리얼 및 핸드북 :
현재 템플릿을 사용하는 유일한 이식 방법은 인라인 함수를 사용하여 헤더 파일로 템플릿을 구현하는 것입니다.
왜 이런거야?
(설명 : 헤더 파일이 유일한 휴대용 솔루션 은 아니지만 가장 편리한 휴대용 솔루션입니다.)
C ++ 표준 라이브러리 에서 인용 : 튜토리얼 및 핸드북 :
현재 템플릿을 사용하는 유일한 이식 방법은 인라인 함수를 사용하여 헤더 파일로 템플릿을 구현하는 것입니다.
왜 이런거야?
(설명 : 헤더 파일이 유일한 휴대용 솔루션 은 아니지만 가장 편리한 휴대용 솔루션입니다.)
답변:
주의 사항 : 구현을 헤더 파일에 넣을 필요 는 없습니다 .이 답변 끝에있는 대체 솔루션을 참조하십시오.
어쨌든 코드가 실패하는 이유는 템플릿을 인스턴스화 할 때 컴파일러가 주어진 템플릿 인수로 새 클래스를 생성하기 때문입니다. 예를 들면 다음과 같습니다.
template<typename T>
struct Foo
{
T bar;
void doSomething(T param) {/* do stuff using T */}
};
// somewhere in a .cpp
Foo<int> f;
이 행을 읽을 때 컴파일러는 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 ++ Super-FAQ을 살펴볼 수 있습니다 .
여기에 많은 정답이 있지만 (완전성을 위해) 이것을 추가하고 싶었습니다.
구현 cpp 파일의 맨 아래에서 템플릿에 사용될 모든 유형을 명시 적으로 인스턴스화하면 링커에서 평소처럼 찾을 수 있습니다.
편집 : 명시 적 템플릿 인스턴스화 예제 추가. 템플릿이 정의되고 모든 멤버 함수가 정의 된 후에 사용됩니다.
template class vector<int>;
이렇게하면 클래스와 모든 멤버 함수가 인스턴스화되어 (링커가 사용할 수있게됩니다) 전용입니다. 템플릿 함수에도 비슷한 구문이 적용되므로 비 멤버 연산자 오버로드가있는 경우 이와 동일한 구문을 수행해야합니다.
일반적인 include 파일 (사전 컴파일 된 헤더?)이 벡터를 사용 extern template class vector<int>
하는 다른 모든 파일 (1000?) 에서 인스턴스화되지 않도록하기 위해 벡터가 헤더에 완전히 정의되어 있기 때문에 위 예제는 상당히 쓸모가 없습니다 .
type
수동으로 나열하지 않고 클래스를 사용할 수 있도록하는 템플릿의 목적을 위반 합니다.
vector
컨테이너는 본질적으로 "모든"유형을 대상으로하기 때문에 좋은 예가 아닙니다. 그러나 숫자 유형 (예 : int8_t, int16_t, int32_t, uint8_t, uint16_t 등)과 같은 특정 유형의 세트에만 해당되는 템플리트를 작성하는 경우가 자주 발생합니다.이 경우에도 템플리트를 사용하는 것이 좋습니다. 그러나 모든 유형의 세트에 대해 명시 적으로 인스턴스화하는 것도 가능하며 내 의견으로는 권장됩니다.
.cpp
파일에 넣고 다른 인스턴스에서 두 개의 인스턴스화를 참조 .cpp
했지만 멤버를 찾을 수 없다는 링크 오류가 계속 발생합니다.
별도의 컴파일이 필요하고 템플릿이 인스턴스화 스타일의 다형성이기 때문입니다.
설명을 위해 콘크리트에 조금 더 가까워 봅시다. 다음 파일이 있다고 가정 해보십시오.
class MyClass<T>
class MyClass<T>
MyClass<int>
별도의 컴파일은 bar.cpp와 독립적으로 foo.cpp 를 컴파일 할 수 있어야 함을 의미합니다 . 컴파일러는 각 컴파일 단위에서 분석, 최적화 및 코드 생성의 모든 어려운 작업을 완전히 독립적으로 수행합니다. 전체 프로그램 분석을 수행 할 필요가 없습니다. 전체 프로그램을 한 번에 처리해야하는 것은 링커 일 뿐이며 링커의 작업이 훨씬 쉽습니다.
foo.cpp를 컴파일 할 때 bar.cpp 가 존재할 필요조차 없지만, 나는 이미 foo.o를 이미 bar 와 함께 연결할 수 있어야 합니다 .o foo 를 다시 컴파일 할 필요없이 방금 생성했습니다. .cpp . foo.cpp 는 동적 라이브러리로 컴파일하고 foo.cpp 없이 다른 곳에 배포 하고 foo.cpp를 쓴 후 몇 년 동안 작성한 코드와 연결할 수 있습니다.
"Instantiation-style polymorphism"은 템플릿 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.o가 연결되어 foo.o 가 매여 할 수 있으며 작동합니다. 이 사실을 사용하여 단일 템플릿을 작성하여 유한 템플릿 인스턴스화를 .cpp 파일로 구현할 수 있습니다. 그러나 bar.cpp 가 템플릿을 템플릿 으로 사용하여 원하는 유형으로 인스턴스화 할 수있는 방법이 없습니다 . foo.cpp 작성자 가 제공 한 템플릿 클래스의 기존 버전 만 사용할 수 있습니다 .
템플릿을 컴파일 할 때 컴파일러는 "모든 버전을 생성"해야하며, 링크하는 동안 사용되지 않은 버전을 필터링해야한다고 생각할 수 있습니다. 포인터 및 배열과 같은 "유형 수정 자"기능을 사용하면 내장 유형만으로도 무한한 유형의 유형을 생성 할 수 있기 때문에 엄청난 오버 헤드와 이러한 접근 방식이 직면하는 극심한 어려움 외에도 이제 프로그램을 확장하면 어떻게됩니까? 추가하여:
class BazPrivate
하고 사용합니다.MyClass<BazPrivate>
우리가 아니면 이것이 작동 할 수있는 가능한 방법은 없습니다
MyClass<T>
MyClass<T>
하는 MyClass<BazPrivate>
동안 baz.cpp를 생성 할 수 있도록 baz.cpp에 전체 템플릿을 포함 해야합니다 (헤더 포함을 통해). .전체 프로그램 분석 컴파일 시스템은 컴파일하는 데 시간이 오래 걸리고 소스 코드없이 컴파일 된 라이브러리를 배포 할 수 없기 때문에 아무도 (1)을 좋아하지 않습니다. 그래서 우리는 (2)를 가지고 있습니다.
템플릿은 실제로 객체 코드로 컴파일하기 전에 컴파일러에서 인스턴스화 해야합니다 . 이 인스턴스화는 템플릿 인수가 알려진 경우에만 달성 할 수 있습니다. 이제 템플릿 함수가로 선언되고 a.h
정의 a.cpp
되고 사용되는 시나리오를 상상해보십시오 b.cpp
. a.cpp
컴파일 될 때 예정된 b.cpp
특정 인스턴스는 물론 다가오는 컴파일 에 템플릿 인스턴스가 필요 하다는 것을 반드시 알 필요는 없습니다 . 더 많은 헤더 및 소스 파일의 경우 상황이 더 복잡해질 수 있습니다.
템플릿의 모든 용도에 대해 컴파일러가 더 똑똑해 보일 수 있다고 주장 할 수 있지만 재귀 적이거나 복잡한 시나리오를 만드는 것은 어렵지 않을 것이라고 확신합니다. AFAIK, 컴파일러는 그러한 전망을하지 않습니다. Anton이 지적한 것처럼 일부 컴파일러는 템플릿 인스턴스화의 명시 적 내보내기 선언을 지원하지만 모든 컴파일러가이를 지원하지는 않습니다 (아직?).
사실, C ++ (11) 이전에 표준이 정의 된 export
키워드 것이 가능 헤더 파일에 템플릿을 선언하고 다른 곳을 구현합니다.
인기있는 컴파일러는이 키워드를 구현하지 않았습니다. 내가 아는 유일한 방법은 Comeison C ++ 컴파일러가 사용하는 Edison Design Group이 작성한 프론트 엔드입니다. 컴파일러는 적절한 인스턴스화를 위해 템플릿 정의가 필요하기 때문에 헤더 파일에 템플릿을 작성해야했습니다.
결과적으로 ISO C ++ 표준위원회는 export
C ++ 11에서 템플릿 의 기능 을 제거하기로 결정했습니다 .
export
실제로 것이다 주어 우리를, 무엇을하지 ... 그리고 지금은 진심 EDG 사람들에 동의 : 그것은 우리를 데려하지 않았을 것 '11에서 자신 대부분의 사람들 ( 포함) 생각 하고 C ++ 표준이 없으면 더 좋습니다.
표준 C ++에는 이러한 요구 사항이 없지만 일부 컴파일러에서는 사용되는 모든 변환 단위에서 모든 함수 및 클래스 템플릿을 사용할 수 있어야합니다. 실제로 이러한 컴파일러의 경우 템플릿 함수 본문을 헤더 파일에서 사용할 수 있어야합니다. 반복 : 즉, 해당 컴파일러는 .cpp 파일과 같은 헤더가 아닌 파일에서 정의 할 수 없습니다.
이 문제를 완화시키기 위한 내보내기 키워드가 있지만 이식성이 거의 없습니다.
컴파일러는 템플릿 매개 변수에 대해 주어진 / 제거 된 매개 변수에 따라 다른 버전의 코드를 인스턴스화해야하므로 템플릿을 헤더에 사용해야합니다. 템플릿은 코드를 직접 나타내는 것이 아니라 해당 코드의 여러 버전에 대한 템플릿입니다. .cpp
파일 에서 템플릿이 아닌 함수를 컴파일하면 구체적인 함수 / 클래스가 컴파일됩니다. 템플릿의 경우에는 다른 유형으로 인스턴스화 할 수 있습니다. 즉, 템플릿 매개 변수를 콘크리트 유형으로 바꿀 때 콘크리트 코드가 생성되어야합니다.
export
별도의 컴파일에 사용되는 키워드 기능이있었습니다 . 이 export
기능은 더 이상 사용되지 않으며 C++11
AFAIK에서는 하나의 컴파일러 만 구현했습니다. 를 사용해서는 안됩니다 export
. 별도의 편집은 가능하지 않다 C++
거나 C++11
하지만 어쩌면의 C++17
개념에서 그것을 만들 경우, 우리는 별도의 컴파일의 몇 가지 방법이 있었다.
별도의 컴파일을 수행하려면 별도의 템플릿 본문 검사가 가능해야합니다. 개념으로 해결책이 가능한 것 같습니다. 최근 표준위원회 회의에서 발표 된 이 문서를 살펴보십시오 . 사용자 코드에서 템플릿 코드의 코드를 인스턴스화해야하기 때문에 이것이 유일한 요구 사항은 아니라고 생각합니다.
템플릿에 대한 별도의 컴파일 문제 또한 현재 작동중인 모듈로의 마이그레이션으로 인해 발생하는 문제라고 생각합니다.
위에 좋은 설명이 많이 있지만 템플릿을 머리글과 본문으로 분리하는 실용적인 방법이 없습니다.
저의 주요 관심사는 정의를 변경할 때 모든 템플릿 사용자의 재 컴파일을 피하는 것입니다.
템플릿 작성자가 템플릿 본문의 사용법과 템플릿 사용자가 템플릿을 수정할 권한이 없는지 알 수 없기 때문에 템플릿 본문에 모든 템플릿 인스턴스화를 갖는 것은 실용적이지 않습니다.
이전 컴파일러 (gcc 4.3.4, aCC A.03.13)에서도 작동하는 다음과 같은 접근 방식을 취했습니다.
각 템플리트 사용법마다 고유 헤더 파일 (UML 모델에서 생성)에 typedef가 있습니다. 그 본문에는 인스턴스화가 포함되어 있습니다 (마지막에 연결된 라이브러리에서 끝납니다).
템플릿의 각 사용자는 해당 헤더 파일을 포함하고 typedef를 사용합니다.
개략적 인 예 :
MyTemplate.h :
#ifndef MyTemplate_h
#define MyTemplate_h 1
template <class T>
class MyTemplate
{
public:
MyTemplate(const T& rt);
void dump();
T t;
};
#endif
MyTemplate.cpp :
#include "MyTemplate.h"
#include <iostream>
template <class T>
MyTemplate<T>::MyTemplate(const T& rt)
: t(rt)
{
}
template <class T>
void MyTemplate<T>::dump()
{
cerr << t << endl;
}
MyInstantiatedTemplate.h :
#ifndef MyInstantiatedTemplate_h
#define MyInstantiatedTemplate_h 1
#include "MyTemplate.h"
typedef MyTemplate< int > MyInstantiatedTemplate;
#endif
MyInstantiatedTemplate.cpp :
#include "MyTemplate.cpp"
template class MyTemplate< int >;
main.cpp :
#include "MyInstantiatedTemplate.h"
int main()
{
MyInstantiatedTemplate m(100);
m.dump();
return 0;
}
이 방법으로 모든 템플릿 사용자 (및 종속성)가 아닌 템플릿 인스턴스화 만 다시 컴파일하면됩니다.
MyInstantiatedTemplate.h
파일과 추가 된 MyInstantiatedTemplate
유형을 제외 하고이 접근법을 좋아 합니다. 당신이 그것을 사용하지 않으면 조금 더 깨끗합니다. 이 보여주는 다른 질문에 대한 내 대답은 체크 아웃 stackoverflow.com/a/41292751/4612476을
여기에 주목할만한 것을 추가하십시오. 함수 템플릿이 아닌 경우 구현 파일에서 템플릿 클래스의 메서드를 잘 정의 할 수 있습니다.
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;
...
}
isEmpty
이외의 다른 번역 단위 에서는 호출 할 수 없습니다 myQueue.cpp
.
.h를 사용하여 모든 .cpp 모듈의 일부로 .h를 컴파일하여 생성되는 추가 컴파일 시간과 이진 크기 팽창이 우려되는 경우, 템플릿 클래스가 템플릿 화되지 않은 기본 클래스에서 내려 오는 것입니다. 인터페이스의 유형에 의존하지 않는 부분들과 그 기본 클래스는 .cpp 파일에서 구현 될 수 있습니다.
class XBase
해야하는 모든 위치를 구현 template class X
하여 유형 종속 부품을 X
모두 나머지에 배치하는 것 XBase
입니다.
컴파일러가 할당 유형을 알아야하기 때문에 정확히 맞습니다. 따라서 헤더 파일은 c / cpp 파일과 달리 컴파일되지 않기 때문에 템플릿 클래스, 함수, 열거 형 등을 헤더 파일에서 공개하거나 라이브러리의 일부 (정적 또는 동적)로 만들려면 헤더 파일에서도 구현해야합니다. 아르. 컴파일러가 타입을 모른다면 컴파일 할 수 없습니다. .Net에서는 모든 객체가 Object 클래스에서 파생되기 때문에 가능합니다. 이것은 .Net이 아닙니다.
컴파일러는 컴파일 단계에서 템플릿을 사용할 때 각 템플릿 인스턴스화에 대한 코드를 생성합니다. 컴파일 및 링크 프로세스에서 .cpp 파일은 main.cpp에 포함 된 .h 파일에 YET 구현이 없기 때문에 참조 또는 정의되지 않은 기호가 포함 된 순수 오브젝트 또는 기계 코드로 변환됩니다. 이들은 템플릿 구현을 정의하는 다른 객체 파일과 연결될 준비가되어 있으므로 전체 a.out 실행 파일이 있습니다.
그러나 정의한 각 템플릿 인스턴스화에 대한 코드를 생성하기 위해 컴파일 단계에서 템플릿을 처리해야하므로 헤더 파일과 별도의 템플릿을 컴파일하는 것은 항상 손을 잡고 이동하기 때문에 작동하지 않습니다. 각 템플릿 인스턴스화는 문자 그대로 완전히 새로운 클래스입니다. 일반 클래스에서는 .h가 해당 클래스의 청사진이고 .cpp가 원시 구현이므로 .h와 .cpp를 구분할 수 있습니다. 따라서 구현 파일을 정기적으로 컴파일하고 링크 할 수는 있지만 템플릿을 사용하면 .h는 클래스는 객체가 템플릿 .cpp 파일이 클래스의 원시 일반 구현이 아니라 단순히 클래스의 청사진 일 뿐이므로 .h 템플릿 파일의 구현은
따라서 템플릿은 개별적으로 컴파일되지 않으며 다른 소스 파일에서 구체적인 인스턴스가있는 경우에만 컴파일됩니다. 그러나 구체적인 인스턴스화는 템플릿 파일의 구현을 알아야합니다.typename T
.h 파일에 구체적 유형을 사용하면 링크 할 .cpp가 있기 때문에 작업을 수행하지 않을 것입니다. 템플릿은 추상적이며 컴파일 할 수 없기 때문에 나중에 찾을 수 없으므로 강제입니다. 지금 구현을 제공하기 위해 컴파일하고 링크해야 할 것을 알고 있으며 구현이 완료되면 소스 파일에 연결됩니다. 기본적으로 템플릿을 인스턴스화하는 순간 완전히 새로운 클래스를 만들어야하며, 컴파일러에 알리지 않으면 제공하는 형식을 사용할 때 해당 클래스가 어떻게 표시되는지 알 수없는 경우 그렇게 할 수 없습니다 템플릿 구현이므로 이제 컴파일러는 T
내 유형으로 대체 하고 컴파일 및 링크 준비가 된 구체적인 클래스를 만들 수 있습니다.
요약하면 템플릿은 클래스의 모양을 나타내는 청사진이고 클래스는 객체의 모양을 나타내는 청사진입니다. 컴파일러는 구체적인 유형 만 컴파일합니다. 즉, 적어도 C ++의 템플릿은 순수한 언어 추상화이기 때문에 템플릿을 구체적인 인스턴스화와 별도로 컴파일 할 수 없습니다. 우리는 말을하기 위해 템플릿의 추상을 제거해야하며, 템플릿 추상화가 정규 클래스 파일로 변환 될 수 있고, 정상적으로 컴파일 될 수 있도록 구체적인 유형을 제공함으로써 그렇게합니다. 템플릿 .h 파일과 템플릿 .cpp 파일을 분리하는 것은 의미가 없습니다. .cpp와 .h의 분리는 .cpp를 개별적으로 컴파일하고 개별적으로 링크 할 수있는 위치에만 있기 때문에 의미가 없습니다. 템플릿은 추상화이기 때문에 템플릿을 개별적으로 컴파일 할 수 없기 때문에
의미 typename T
는 컴파일 단계에서 연결 단계 T
가 아니라 대체됩니다. 따라서 컴파일러에게 전혀 의미가없는 구체적인 값 유형으로 바꾸지 않고 템플릿을 컴파일하려고 하면 결과 코드가 생성되지 않기 때문에 객체 코드를 만들 수 없습니다 알고 T
있다.
기술적으로 template.cpp 파일을 저장하고 다른 소스에서 찾을 때 유형을 전환하는 일종의 기능을 만들 수 있습니다. 표준에는 export
템플릿을 별도의 위치에 넣을 수 있는 키워드가 있다고 생각합니다 cpp 파일이지만 많은 컴파일러가 실제로 이것을 구현하지는 않습니다.
참고로 템플릿 클래스에 대한 전문화를 수행 할 때 헤더를 구현에서 분리 할 수 있습니다. 정의에 의한 전문화는 개별적으로 컴파일하고 링크 할 수있는 구체적 유형을 전문화한다는 의미이기 때문입니다.
별도의 구현 방법은 다음과 같습니다.
//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에는 forward 선언이 있습니다. foo.tpp는 구현이 있으며 inner_foo.h를 포함합니다. foo.h는 foo.tpp를 포함하기 위해 한 줄만 가질 것입니다.
컴파일 타임에 foo.h의 내용은 foo.tpp에 복사 된 다음 전체 파일이 foo.h에 복사 된 후 컴파일됩니다. 이런 식으로, 하나의 추가 파일과 교환 할 때 제한이 없으며 이름이 일관됩니다.
코드의 정적 분석기가 클래스의 순방향 선언이 * .tpp에 표시되지 않으면 중단되기 때문에이 작업을 수행합니다. IDE에서 코드를 작성하거나 YouCompleteMe 또는 기타를 사용할 때 성가신 일입니다.
템플릿 인스턴스화를위한 "cfront"모델과 "borland"모델 간의 장단점을 설명하는이 gcc 페이지를 참조하십시오.
https://gcc.gnu.org/onlinedocs/gcc-4.6.4/gcc/Template-Instantiation.html
"borland"모델은 저자가 제안한 것과 일치하며, 완전한 템플릿 정의를 제공하고 여러 번 컴파일 된 것들을 갖습니다.
수동 및 자동 템플릿 인스턴스화 사용과 관련된 명시적인 권장 사항이 포함되어 있습니다. 예를 들어, "-repo"옵션을 사용하여 인스턴스화해야하는 템플릿을 수집 할 수 있습니다. 또는 다른 옵션은 "-fno-implicit-templates"를 사용하여 자동 템플릿 인스턴스화를 비활성화하여 수동 템플릿 인스턴스화를 강제하는 것입니다.
필자는 경험에 따라 C ++ 표준 라이브러리 및 부스트 템플릿을 사용하여 각 컴파일 단위마다 인스턴스화되고 있습니다 (템플릿 라이브러리 사용). 큰 템플릿 클래스의 경우 필요한 유형에 대해 수동 템플릿 인스턴스화를 한 번 수행합니다.
다른 프로그램에서 사용할 템플릿 라이브러리가 아닌 작업 프로그램을 제공하고 있기 때문에 이것은 나의 접근 방식입니다. 이 책의 저자 인 Josuttis는 템플릿 라이브러리에서 많은 작업을 수행합니다.
속도가 정말 걱정된다면 미리 컴파일 된 헤더를 사용하여 탐색한다고 가정합니다 https://gcc.gnu.org/onlinedocs/gcc/Precompiled-Headers.html
많은 컴파일러에서 지원을 받고 있습니다. 그러나 템플릿 헤더 파일에서는 사전 컴파일 된 헤더가 어려울 것이라고 생각합니다.
헤더 파일에 선언과 정의를 모두 작성하는 것이 좋은 이유는 읽기 쉽기 때문입니다. Utility.h에 이러한 템플릿 기능이 있다고 가정합니다.
template <class T>
T min(T const& one, T const& theOther);
그리고 Utility.cpp에서 :
#include "Utility.h"
template <class T>
T min(T const& one, T const& other)
{
return one < other ? one : other;
}
여기에서는 모든 T 클래스에서보다 작음 연산자 (<)를 구현해야합니다. "<"를 구현하지 않은 두 개의 클래스 인스턴스를 비교하면 컴파일러 오류가 발생합니다.
따라서 템플릿 선언과 정의를 분리하면 자신의 클래스에서이 API를 사용하기 위해 헤더 파일을 읽고이 템플릿의 내용을 볼 수 없으며 컴파일러에서 알려줍니다 어떤 연산자를 재정의해야하는지에 대한 사례입니다.
실제로 템플릿 클래스를 .cpp 파일이 아닌 .template 파일 내에 정의 할 수 있습니다. 헤더 파일 내에서만 정의 할 수 있다고 말하는 사람은 잘못되었습니다. 이것은 C ++ 98로 거슬러 올라가는 것입니다.
컴파일러가 지능적으로 .template 파일을 c ++ 파일로 처리하도록하는 것을 잊지 마십시오.
다음은 동적 배열 클래스에 대한 예입니다.
#ifndef dynarray_h
#define dynarray_h
#include <iostream>
template <class T>
class DynArray{
int capacity_;
int size_;
T* data;
public:
explicit DynArray(int size = 0, int capacity=2);
DynArray(const DynArray& d1);
~DynArray();
T& operator[]( const int index);
void operator=(const DynArray<T>& d1);
int size();
int capacity();
void clear();
void push_back(int n);
void pop_back();
T& at(const int n);
T& back();
T& front();
};
#include "dynarray.template" // this is how you get the header file
#endif
이제 .template 파일 내부에서 평소와 같이 함수를 정의합니다.
template <class T>
DynArray<T>::DynArray(int size, int capacity){
if (capacity >= size){
this->size_ = size;
this->capacity_ = capacity;
data = new T[capacity];
}
// for (int i = 0; i < size; ++i) {
// data[i] = 0;
// }
}
template <class T>
DynArray<T>::DynArray(const DynArray& d1){
//clear();
//delete [] data;
std::cout << "copy" << std::endl;
this->size_ = d1.size_;
this->capacity_ = d1.capacity_;
data = new T[capacity()];
for(int i = 0; i < size(); ++i){
data[i] = d1.data[i];
}
}
template <class T>
DynArray<T>::~DynArray(){
delete [] data;
}
template <class T>
T& DynArray<T>::operator[]( const int index){
return at(index);
}
template <class T>
void DynArray<T>::operator=(const DynArray<T>& d1){
if (this->size() > 0) {
clear();
}
std::cout << "assign" << std::endl;
this->size_ = d1.size_;
this->capacity_ = d1.capacity_;
data = new T[capacity()];
for(int i = 0; i < size(); ++i){
data[i] = d1.data[i];
}
//delete [] d1.data;
}
template <class T>
int DynArray<T>::size(){
return size_;
}
template <class T>
int DynArray<T>::capacity(){
return capacity_;
}
template <class T>
void DynArray<T>::clear(){
for( int i = 0; i < size(); ++i){
data[i] = 0;
}
size_ = 0;
capacity_ = 2;
}
template <class T>
void DynArray<T>::push_back(int n){
if (size() >= capacity()) {
std::cout << "grow" << std::endl;
//redo the array
T* copy = new T[capacity_ + 40];
for (int i = 0; i < size(); ++i) {
copy[i] = data[i];
}
delete [] data;
data = new T[ capacity_ * 2];
for (int i = 0; i < capacity() * 2; ++i) {
data[i] = copy[i];
}
delete [] copy;
capacity_ *= 2;
}
data[size()] = n;
++size_;
}
template <class T>
void DynArray<T>::pop_back(){
data[size()-1] = 0;
--size_;
}
template <class T>
T& DynArray<T>::at(const int n){
if (n >= size()) {
throw std::runtime_error("invalid index");
}
return data[n];
}
template <class T>
T& DynArray<T>::back(){
if (size() == 0) {
throw std::runtime_error("vector is empty");
}
return data[size()-1];
}
template <class T>
T& DynArray<T>::front(){
if (size() == 0) {
throw std::runtime_error("vector is empty");
}
return data[0];
}