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


1776

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

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

왜 이런거야?

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


13
모든 템플릿 함수 정의를 헤더 파일에 배치하는 것이 가장 편리한 방법 일 수도 있지만 해당 인용에서 "인라인"이 무엇을하고 있는지는 아직 명확하지 않습니다. 이를 위해 인라인 함수를 사용할 필요가 없습니다. "인라인"은 이것과 전혀 관련이 없습니다.
AnT

7
책이 오래되었습니다.
gerardw

1
템플릿은 바이트 코드로 컴파일 할 수있는 함수와 다릅니다. 그러한 함수를 생성하는 것은 단지 패턴입니다. 템플릿을 * .cpp 파일에 단독으로 넣으면 컴파일 할 것이 없습니다. 또한 명시 적 인스턴스화는 실제로 템플릿이 아니라 * .obj 파일로 끝나는 템플릿에서 기능을 시작하는 출발점입니다.
dgrat

5
이것으로 인해 C ++에서 템플릿 개념이 손상되었다고 생각하는 유일한 사람입니까?
DragonGamer

답변:


1557

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

어쨌든 코드가 실패하는 이유는 템플릿을 인스턴스화 할 때 컴파일러가 주어진 템플릿 인수로 새 클래스를 생성하기 때문입니다. 예를 들면 다음과 같습니다.

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을 살펴볼 수 있습니다 .


96
실제로 명시 적 인스턴스화는 헤더가 아닌 모든 Foo 멤버 함수의 정의에 액세스 할 수있는 .cpp 파일에 있어야합니다.
Mankarse

11
"컴파일러는 템플릿 인수 (이 경우 int)로 메소드를 인스턴스화하려면 메소드의 구현에 액세스 할 수 있어야합니다. 이러한 구현이 헤더에없는 경우 액세스 할 수 없습니다." 컴파일러가 .cpp 파일에 액세스 할 수 없습니까? 컴파일러는 .cpp 정보에 액세스 할 수 있습니다. 다른 방법으로 .obj 파일로 변환하는 방법은 무엇입니까? 편집 :이 질문에 대한 답변은이 답변에서 제공하는 링크에 있습니다 ...
xcrypt

31
나는 이것이 분명 중요한 일이 분명이 게시물에 언급되지 않은 컴파일 단위와 관련이있는 문제에 대해 설명 생각하지 않는다
zinking

6
@Gabson : 구조체와 클래스는 클래스에 대한 기본 액세스 수정자가 "비공개"이고 구조체에 대해 공용 인 것을 제외하고는 동일합니다. 이 질문 을 보면 배울 수있는 몇 가지 작은 차이점이 있습니다 .
Luc Touraille

3
이 질문의 시작 부분에 질문이 잘못된 전제에 기반하고 있음을 분명히하기 위해 문장을 추가했습니다. 누군가 "X가 왜 참입니까?"라고 물으면 실제로 X가 사실이 아닌 경우, 우리는 그 가정을 빨리 거부해야합니다.
Aaron McDaid

250

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

구현 cpp 파일의 맨 아래에서 템플릿에 사용될 모든 유형을 명시 적으로 인스턴스화하면 링커에서 평소처럼 찾을 수 있습니다.

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

template class vector<int>;

이렇게하면 클래스와 모든 멤버 함수가 인스턴스화되어 (링커가 사용할 수있게됩니다) 전용입니다. 템플릿 함수에도 비슷한 구문이 적용되므로 비 멤버 연산자 오버로드가있는 경우 이와 동일한 구문을 수행해야합니다.

일반적인 include 파일 (사전 컴파일 된 헤더?)이 벡터를 사용 extern template class vector<int>하는 다른 모든 파일 (1000?) 에서 인스턴스화되지 않도록하기 위해 벡터가 헤더에 완전히 정의되어 있기 때문에 위 예제는 상당히 쓸모가 없습니다 .


51
어. 좋은 대답이지만 실제 깨끗한 해결책은 없습니다. 템플릿에 대해 가능한 모든 유형을 나열하는 것은 템플릿의 형식과 일치하지 않는 것 같습니다.
Jiminion

6
이것은 많은 경우에 좋을 수 있지만 일반적으로 템플릿을 type수동으로 나열하지 않고 클래스를 사용할 수 있도록하는 템플릿의 목적을 위반 합니다.
Tomáš Zato-Reinstate Monica

7
vector컨테이너는 본질적으로 "모든"유형을 대상으로하기 때문에 좋은 예가 아닙니다. 그러나 숫자 유형 (예 : int8_t, int16_t, int32_t, uint8_t, uint16_t 등)과 같은 특정 유형의 세트에만 해당되는 템플리트를 작성하는 경우가 자주 발생합니다.이 경우에도 템플리트를 사용하는 것이 좋습니다. 그러나 모든 유형의 세트에 대해 명시 적으로 인스턴스화하는 것도 가능하며 내 의견으로는 권장됩니다.
UncleZeiv 2016 년

템플리트가 정의되고 "모든 멤버 함수가 정의 된"후에 사용됩니다. 감사 !
Vitt Volt

1
뭔가 빠진 것 같은 느낌이 들었습니다. 두 가지 유형에 대한 명시 적 인스턴스화를 클래스 .cpp파일에 넣고 다른 인스턴스에서 두 개의 인스턴스화를 참조 .cpp했지만 멤버를 찾을 수 없다는 링크 오류가 계속 발생합니다.
oarfish

250

별도의 컴파일이 필요하고 템플릿이 인스턴스화 스타일의 다형성이기 때문입니다.

설명을 위해 콘크리트에 조금 더 가까워 봅시다. 다음 파일이 있다고 가정 해보십시오.

  • foo.h
    • 의 인터페이스를 선언 class MyClass<T>
  • foo.cpp
    • 구현을 정의 class MyClass<T>
  • bar.cpp
    • 사용 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 작성자 가 제공 한 템플릿 클래스의 기존 버전 만 사용할 수 있습니다 .

템플릿을 컴파일 할 때 컴파일러는 "모든 버전을 생성"해야하며, 링크하는 동안 사용되지 않은 버전을 필터링해야한다고 생각할 수 있습니다. 포인터 및 배열과 같은 "유형 수정 자"기능을 사용하면 내장 유형만으로도 무한한 유형의 유형을 생성 할 수 있기 때문에 엄청난 오버 헤드와 이러한 접근 방식이 직면하는 극심한 어려움 외에도 이제 프로그램을 확장하면 어떻게됩니까? 추가하여:

  • baz.cpp
    • 선언하고 구현 class BazPrivate하고 사용합니다.MyClass<BazPrivate>

우리가 아니면 이것이 작동 할 수있는 가능한 방법은 없습니다

  1. 다시 컴파일해야 foo.cpp에 우리가 변경할 때마다 프로그램에서 다른 파일을 경우, 그것은 새로운 새로운 인스턴스를 추가MyClass<T>
  2. baz.cpp 가 컴파일 MyClass<T>하는 MyClass<BazPrivate>동안 baz.cpp를 생성 할 수 있도록 baz.cpp에 전체 템플릿을 포함 해야합니다 (헤더 포함을 통해). .

전체 프로그램 분석 컴파일 시스템은 컴파일하는 데 시간이 오래 걸리고 소스 코드없이 컴파일 된 라이브러리를 배포 할 수 없기 때문에 아무도 (1)을 좋아하지 않습니다. 그래서 우리는 (2)를 가지고 있습니다.


50
강조 인용 템플릿은 문자 그대로 템플릿입니다. 클래스 템플릿은, 우리가 직면 각 T에 대한 새로운 클래스를 만들기위한 레시피 클래스입니다하지 않습니다
v.oddou

알고 싶습니다. 클래스의 헤더 또는 소스 파일 이외의 곳에서 명시 적 인스턴스화를 수행 할 수 있습니까? 예를 들어 main.cpp에서 수행합니까?
gromit190

1
@Birger 전체 템플릿 구현에 액세스 할 수있는 모든 파일 (동일한 파일에 있거나 헤더 포함을 통해)에서이를 수행 할 수 있어야합니다.
Ben

11
@ajeh 수사가 아닙니다. 문제는 "헤더에서 템플릿을 구현해야하는 이유"입니다. 따라서 C ++ 언어가이 요구 사항을 이끌어내는 기술적 선택에 대해 설명했습니다. 내 답변을 작성하기 전에 다른 사람들은 완전한 솔루션이 될 수 없기 때문에 완전한 솔루션이 아닌 해결 방법을 이미 제공했습니다 . 나는 그 대답이 질문의 "왜"각도에 대한 더 자세한 논의로 보완 될 것이라고 느꼈다.
Ben

1
템플릿을 사용하지 않았다면 (필요한 것을 효율적으로 코딩하기 위해) 어쨌든 해당 클래스의 몇 가지 버전 만 제공 할 것입니다. 3 가지 옵션이 있습니다. 1). 템플릿을 사용하지 마십시오. (다른 모든 클래스 / 함수와 마찬가지로 아무도 다른 사람들이 유형을 변경할 수 없도록 신경 쓰지 않습니다) 2). 템플릿을 사용하고 사용할 수있는 유형을 문서화합니다. 삼). 그들에게 전체 구현 (소스) 보너스를 제공하십시오 4). 그들이 당신의 수업 중 하나에서 템플릿을 만들고 싶을 경우를 대비하여 전체 소스를 제공하십시오.)
Puddle

81

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

템플릿의 모든 용도에 대해 컴파일러가 더 똑똑해 보일 수 있다고 주장 할 수 있지만 재귀 적이거나 복잡한 시나리오를 만드는 것은 어렵지 않을 것이라고 확신합니다. AFAIK, 컴파일러는 그러한 전망을하지 않습니다. Anton이 지적한 것처럼 일부 컴파일러는 템플릿 인스턴스화의 명시 적 내보내기 선언을 지원하지만 모든 컴파일러가이를 지원하지는 않습니다 (아직?).


1
"export"는 표준이지만 구현하기가 어렵 기 때문에 대부분의 컴파일러 팀은 아직 수행하지 않았습니다.
vava

5
내보내기는 소스 공개의 필요성을 제거하지 않으며 컴파일 종속성을 줄이는 것도 아니며 컴파일러 빌더의 노력이 필요합니다. 그래서 Herb Sutter 자신은 컴파일러 빌더에게 수출을 '잊어 버렸습니다'라고 요청했습니다. 투자가 필요한 시간이 다른 곳에서 더 잘 지출 될 것입니다 ...
Pieter

2
따라서 수출이 '아직'구현되지 않았다고 생각합니다. 그것은 다른 사람들이 얼마나 오래 걸렸고 얼마나 많이 얻었는지 알게 된 후에 EDG 이외의 다른 사람에 의해 결코 이루어질 수 없을 것입니다.
Pieter

3
관심이 있으시다면이 논문은 "우리가 수출을 감당할 수없는 이유"라고하며, 그의 블로그 ( gotw.ca/publications ) 에수록되어 있지만 pdf는 없습니다 (빠른 구글은 그 자료를 보여 주어야합니다)
Pieter

1
좋은 모범과 설명 감사합니다. 여기 내 질문이 있습니다 : 왜 컴파일러가 템플릿이 호출되는지 알아낼 수 없으며 정의 파일을 컴파일하기 전에 먼저 해당 파일을 컴파일 할 수 있습니까? 나는 그것이 간단한 경우에 이루어질 수 있다고 상상할 수있다.
Vlad

62

사실, C ++ (11) 이전에 표준이 정의 된 export키워드 것이 가능 헤더 파일에 템플릿을 선언하고 다른 곳을 구현합니다.

인기있는 컴파일러는이 키워드를 구현하지 않았습니다. 내가 아는 유일한 방법은 Comeison C ++ 컴파일러가 사용하는 Edison Design Group이 작성한 프론트 엔드입니다. 컴파일러는 적절한 인스턴스화를 위해 템플릿 정의가 필요하기 때문에 헤더 파일에 템플릿을 작성해야했습니다.

결과적으로 ISO C ++ 표준위원회는 exportC ++ 11에서 템플릿 의 기능 을 제거하기로 결정했습니다 .


6
... 그리고 몇 년 후, 나는 마침내 무엇을 이해 export실제로 것이다 주어 우리를, 무엇을하지 ... 그리고 지금은 진심 EDG 사람들에 동의 : 그것은 우리를 데려하지 않았을 것 '11에서 자신 대부분의 사람들 ( 포함) 생각 하고 C ++ 표준이 없으면 더 좋습니다.
DevSolar 10

4
@DevSolar :이 논문은 정치적이고 반복적이며 잘못 쓰여졌습니다. 그것은 일반적인 표준 수준의 산문이 아닙니다. 불필요하게 길고 지루하며 기본적으로 같은 내용이 수십 페이지에 걸쳐 3 배라고 말합니다. 그러나 나는 수출이 수출이 아니라는 것을 알게되었다. 좋은 정보 야!
v.oddou

1
@ v.oddou : 훌륭한 개발자와 훌륭한 기술 저술가는 별개의 기술 집합입니다. 어떤 사람들은 둘 다 할 수 있고, 많은 사람들은 할 수 없습니다. ;-)
DevSolar

@ v.oddou이 논문은 잘못 작성된 것이 아니라 정보가 잘못되었습니다. 또한 수출에 반대하는 것처럼 들리는 방식으로 실제로 수출에 대한 매우 강력한 주장이 혼합되어 있습니다.“수출이있을 때 표준에 수많은 ODR 관련 구멍을 발견합니다. 내보내기 전에 컴파일러가 ODR 위반을 진단 할 필요가 없었습니다. 이제 다른 번역 단위의 내부 데이터 구조를 결합해야하고 실제로 다른 것을 나타내는 경우 결합 할 수 없으므로 확인이 필요하기 때문에 필요합니다.”
curiousguy

" 이제 발생한 번역 단위를 추가해야합니다 ."Duh. 불충분 한 주장을 강요 당할 때는 아무런 주장도 없다. 물론 오류에서 파일 이름을 언급 할 것입니다. 거래는 무엇입니까? 그 BS에 빠진 사람은 마음이 흔들리는 것입니다. " James Kanze와 같은 전문가조차도 수출이 실제로 이와 같은 것을 받아들이 기가 어렵다는 것을 알게되었습니다. "
curiousguy

34

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

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


키워드 "inline"을 사용하여 .cpp 파일로 구현할 수없는 이유는 무엇입니까?
MainID

2
당신은 할 수 있고, 심지어 "인라인"을 넣을 필요가 없습니다. 그러나 당신은 그 cpp 파일과 다른 곳에서 그것들을 사용할 수 있습니다.
vava

10
이는 "컴파일러가 .cpp 파일과 같은 헤더가 아닌 파일에 정의 할 수 없다는 의미"라는 말을 제외하고 는 가장 정확한 답변입니다.
궤도에서 가벼움 경주

28

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

export별도의 컴파일에 사용되는 키워드 기능이있었습니다 . 이 export기능은 더 이상 사용되지 않으며 C++11AFAIK에서는 하나의 컴파일러 만 구현했습니다. 를 사용해서는 안됩니다 export. 별도의 편집은 가능하지 않다 C++거나 C++11하지만 어쩌면의 C++17개념에서 그것을 만들 경우, 우리는 별도의 컴파일의 몇 가지 방법이 있었다.

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

템플릿에 대한 별도의 컴파일 문제 또한 현재 작동중인 모듈로의 마이그레이션으로 인해 발생하는 문제라고 생각합니다.


15

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

template < typename ... >
class MyClass
{

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

15

위에 좋은 설명이 많이 있지만 템플릿을 머리글과 본문으로 분리하는 실용적인 방법이 없습니다.
저의 주요 관심사는 정의를 변경할 때 모든 템플릿 사용자의 재 컴파일을 피하는 것입니다.
템플릿 작성자가 템플릿 본문의 사용법과 템플릿 사용자가 템플릿을 수정할 권한이 없는지 알 수 없기 때문에 템플릿 본문에 모든 템플릿 인스턴스화를 갖는 것은 실용적이지 않습니다.
이전 컴파일러 (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;
}

이 방법으로 모든 템플릿 사용자 (및 종속성)가 아닌 템플릿 인스턴스화 만 다시 컴파일하면됩니다.


1
나는 MyInstantiatedTemplate.h파일과 추가 된 MyInstantiatedTemplate유형을 제외 하고이 접근법을 좋아 합니다. 당신이 그것을 사용하지 않으면 조금 더 깨끗합니다. 이 보여주는 다른 질문에 대한 내 대답은 체크 아웃 stackoverflow.com/a/41292751/4612476을
카메론 Tacklind

이것은 두 세계에서 가장 좋습니다. 이 답변이 더 높은 등급을 받기를 바랍니다. 동일한 아이디어를 약간 더 깔끔하게 구현하려면 위의 링크를 참조하십시오.
Wormer

8

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


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;

    ...
}

2
실제 사람의 경우 ??? 그렇다면 귀하의 답변은 올바른 것으로 확인해야합니다. 왜 .cpp에서 템플릿이 아닌 멤버 메소드를 정의 할 수 있다면 누구도 해킹이 필요한 모든 것들이 필요합니까?
Michael IV

적어도 MSVC 2019에서는 템플릿 클래스의 멤버 함수에 대한 해결되지 않은 외부 기호가 표시됩니다.
Michael IV

테스트 할 MSVC 2019가 없습니다. 이것은 C ++ 표준에 의해 허용됩니다. 이제 MSVC는 규칙을 항상 준수하지 않는 것으로 유명합니다. 아직 프로젝트 설정이 아닌 경우 프로젝트 설정-> C / C ++-> 언어-> 적합성 모드-> 예 (허용)를 시도하십시오.
Nikos

1
이 정확한 예제는 작동하지만 다음 isEmpty이외의 다른 번역 단위 에서는 호출 할 수 없습니다 myQueue.cpp.
MM

7

.h를 사용하여 모든 .cpp 모듈의 일부로 .h를 컴파일하여 생성되는 추가 컴파일 시간과 이진 크기 팽창이 우려되는 경우, 템플릿 클래스가 템플릿 화되지 않은 기본 클래스에서 내려 오는 것입니다. 인터페이스의 유형에 의존하지 않는 부분들과 그 기본 클래스는 .cpp 파일에서 구현 될 수 있습니다.


2
이 응답은 훨씬 더 많이 수정되어야합니다. 나는 " 독립적으로 "같은 접근법을 발견했고, 공식적인 패턴 인지, 이름이 있는지 궁금하기 때문에 이미 다른 사람이 이미 사용한 것을 찾고있었습니다 . 내 접근 방식은 구현 class XBase해야하는 모든 위치를 구현 template class X하여 유형 종속 부품을 X모두 나머지에 배치하는 것 XBase입니다.
Fabio A.

6

컴파일러가 할당 유형을 알아야하기 때문에 정확히 맞습니다. 따라서 헤더 파일은 c / cpp 파일과 달리 컴파일되지 않기 때문에 템플릿 클래스, 함수, 열거 형 등을 헤더 파일에서 공개하거나 라이브러리의 일부 (정적 또는 동적)로 만들려면 헤더 파일에서도 구현해야합니다. 아르. 컴파일러가 타입을 모른다면 컴파일 할 수 없습니다. .Net에서는 모든 객체가 Object 클래스에서 파생되기 때문에 가능합니다. 이것은 .Net이 아닙니다.


5
"헤더 파일은 컴파일되지 않습니다"-그것을 설명하는 정말 이상한 방법입니다. 헤더 파일은 "c / cpp"파일과 같이 변환 단위의 일부일 수 있습니다.
Flexo

2
사실, 헤더 파일은 여러 번 매우 자주 컴파일되는 반면 소스 파일은 보통 한 번 컴파일되는 것이 사실과 거의 반대입니다.
xaxxon

6

컴파일러는 컴파일 단계에서 템플릿을 사용할 때 각 템플릿 인스턴스화에 대한 코드를 생성합니다. 컴파일 및 링크 프로세스에서 .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 파일이지만 많은 컴파일러가 실제로 이것을 구현하지는 않습니다.

참고로 템플릿 클래스에 대한 전문화를 수행 할 때 헤더를 구현에서 분리 할 수 ​​있습니다. 정의에 의한 전문화는 개별적으로 컴파일하고 링크 할 수있는 구체적 유형을 전문화한다는 의미이기 때문입니다.


4

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

//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 또는 기타를 사용할 때 성가신 일입니다.


2
s / inner_foo / foo / g 및 foo.h의 끝에 foo.tpp를 포함하십시오. 하나 적은 파일.

1

템플릿 인스턴스화를위한 "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

많은 컴파일러에서 지원을 받고 있습니다. 그러나 템플릿 헤더 파일에서는 사전 컴파일 된 헤더가 어려울 것이라고 생각합니다.


-2

헤더 파일에 선언과 정의를 모두 작성하는 것이 좋은 이유는 읽기 쉽기 때문입니다. 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를 사용하기 위해 헤더 파일을 읽고이 템플릿의 내용을 볼 수 없으며 컴파일러에서 알려줍니다 어떤 연산자를 재정의해야하는지에 대한 사례입니다.


-7

실제로 템플릿 클래스를 .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];
    }

2
대부분의 사람들은 헤더 파일을 정의를 소스 파일로 전파하는 것으로 정의합니다. 따라서 파일 확장자 ".template"를 사용하기로 결정했지만 헤더 파일을 작성했습니다.
Tommy
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.