C++ 표준 라이브러리 에서 인용: 자습서 및 핸드북:
현재 템플릿을 사용하는 유일한 이식 가능한 방법은 인라인 함수를 사용하여 헤더 파일에서 템플릿을 구현하는 것입니다.
왜 이런거야?
(설명: 헤더 파일이 유일한 휴대용 솔루션은 아니지만 가장 편리한 휴대용 솔루션입니다.)
질문자 :MainID
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)에 클래스를 구현하고 헤더 끝에 이 구현 파일을 포함하는 것입니다.
푸.h
template <typename T> struct Foo { void doSomething(T param); }; #include "Foo.tpp"
푸.tpp
template <typename T> void Foo<T>::doSomething(T param) { //implementation }
이런 식으로 구현은 여전히 선언과 분리되지만 컴파일러에서 액세스할 수 있습니다.
또 다른 솔루션은 구현을 분리된 상태로 유지하고 필요한 모든 템플릿 인스턴스를 명시적으로 인스턴스화하는 것입니다.
푸.h
// no implementation template <typename T> struct 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를 볼 수 있습니다.
별도의 컴파일이 필요하고 템플릿이 인스턴스화 스타일의 다형성이기 때문입니다.
설명을 위해 콘크리트에 조금 더 가까이 가자. 다음 파일이 있다고 가정해 보겠습니다.
class MyClass<T>
의 인터페이스를 선언합니다.class MyClass<T>
의 구현을 정의합니다.MyClass<int>
별도의 컴파일은 내가 foo.cpp 를 bar.cpp 와 독립적으로 컴파일할 수 있어야 한다는 것을 의미합니다. 컴파일러는 각 컴파일 단위에서 분석, 최적화 및 코드 생성의 모든 힘든 작업을 완전히 독립적으로 수행합니다. 우리는 전체 프로그램 분석을 할 필요가 없습니다. 전체 프로그램을 한 번에 처리해야 하는 것은 링커뿐이며 링커의 작업은 훨씬 더 쉽습니다.
bar.cpp 는 내가 foo.cpp를 컴파일할 때 존재할 필요조차 없지만, 여전히 foo.o 를 연결할 수 있어야 합니다. 이미 bar.o 와 함께 사용했던 foo.o는 foo 를 다시 컴파일할 필요 없이 방금 생성했습니다. .cpp . foo.cpp 는 동적 라이브러리로 컴파일되어 foo.cpp 없이 다른 곳에 배포되고 내가 foo.cpp를 작성한 후 몇 년이 지난 후에 작성하는 코드와 연결될 수도 있습니다.
"인스턴스화 스타일 다형성"은 템플릿 MyClass<T>
T
값에 대해 작동할 수 있는 코드로 컴파일할 수 있는 일반 클래스가 아님을 의미합니다. 이는 권투, 할당자와 생성자에 대한 함수 포인터 전달 등의 오버헤드를 추가할 것입니다. C++ 템플릿의 의도는 거의 동일한 class MyClass_int
, class MyClass_float
등을 작성하지 않아도 되지만 여전히 대부분의 경우 우리가 별도로 각 버전을 작성했다있다 컴파일 된 코드. 따라서 템플릿은 말 그대로 템플릿입니다. 클래스 템플릿은 클래스가 아니라 우리가 만나는 T
에 대해 새 클래스를 만드는 방법입니다. 템플릿은 코드로 컴파일할 수 없으며 템플릿을 인스턴스화한 결과만 컴파일할 수 있습니다.
따라서 foo.cpp 가 컴파일될 때 컴파일러는 myClass MyClass<int>
가 필요하다는 것을 알기 위해 bar.cpp를 볼 수 없습니다. 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의 컴파일시.전체 프로그램 분석 컴파일 시스템은 컴파일하는 데 시간이 오래 걸리고 소스 코드 없이 컴파일된 라이브러리를 배포할 수 없기 때문에 아무도 (1)을 좋아하지 않습니다. 그래서 우리는 대신 (2)가 있습니다.
여기에 많은 정답이 있지만 (완전성을 위해) 이것을 추가하고 싶었습니다.
구현 cpp 파일의 맨 아래에서 템플릿과 함께 사용할 모든 유형의 명시적 인스턴스화를 수행하면 링커가 평소와 같이 해당 유형을 찾을 수 있습니다.
편집: 명시적 템플릿 인스턴스화의 예 추가. 템플릿이 정의되고 모든 멤버 함수가 정의된 후에 사용됩니다.
template class vector<int>;
이렇게 하면 클래스와 모든 해당 멤버 함수(만)를 인스턴스화하고 링커에서 사용할 수 있습니다. 유사한 구문이 함수 템플릿에 대해 작동하므로 비멤버 연산자 오버로드가 있는 경우 해당 오버로드에 대해서도 동일한 작업을 수행해야 할 수 있습니다.
일반적인 포함 파일(미리 컴파일된 헤더?)이 extern template class vector<int>
하여 다른 모든 파일(1000?) 벡터를 사용하는 것입니다.
템플릿은 실제로 개체 코드로 컴파일하기 전에 컴파일러에서 인스턴스화해야 합니다. 이 인스턴스화는 템플릿 인수가 알려진 경우에만 달성할 수 있습니다. 이제 템플릿 함수가 선언 된 시나리오를 상상 ah
에 정의 a.cpp
과에서 사용 b.cpp
. a.cpp
가 컴파일될 때 다음 컴파일 b.cpp에 템플릿의 인스턴스가 b.cpp
는 반드시 알 필요는 없습니다. 더 많은 헤더 및 소스 파일의 경우 상황이 빠르게 더 복잡해질 수 있습니다.
템플릿의 모든 사용에 대해 "미리보기"하도록 컴파일러를 더 똑똑하게 만들 수 있다고 주장할 수 있지만 재귀적이거나 복잡한 시나리오를 만드는 것이 어렵지 않을 것이라고 확신합니다. AFAIK, 컴파일러는 그러한 미리보기를 수행하지 않습니다. Anton이 지적했듯이 일부 컴파일러는 템플릿 인스턴스화의 명시적 내보내기 선언을 지원하지만 모든 컴파일러가 이를 지원하는 것은 아닙니다(아직?).
사실, 이전의 C ++ 11 표준은 정의 export
이 가능 헤더 파일에 템플릿을 선언하고 다른 곳을 구현하는 것이 키워드를.
인기 있는 컴파일러 중 어느 것도 이 키워드를 구현하지 않았습니다. 내가 아는 유일한 것은 Comeau C++ 컴파일러에서 사용하는 Edison Design Group이 작성한 프론트엔드입니다. 컴파일러는 적절한 인스턴스화를 위해 템플릿 정의가 필요하기 때문에 다른 모든 것들은 헤더 파일에 템플릿을 작성해야 했습니다(다른 사람들이 이미 지적했듯이).
그 결과 ISO C++ 표준 위원회는 C++11에서 템플릿 export
표준 C++에는 그러한 요구 사항이 없지만 일부 컴파일러에서는 사용되는 모든 번역 단위에서 모든 함수 및 클래스 템플릿을 사용할 수 있어야 합니다. 실제로 이러한 컴파일러의 경우 템플릿 함수의 본문을 헤더 파일에서 사용할 수 있어야 합니다. 반복하려면 해당 컴파일러가 .cpp 파일과 같은 헤더가 아닌 파일에 정의할 수 없다는 의미입니다.
이 문제를 완화하기 위한 내보내기 키워드가 있지만 이식성에는 가깝지 않습니다.
템플릿은 컴파일러가 템플릿 매개변수에 대해 주어진/추론된 매개변수에 따라 다른 버전의 코드를 인스턴스화해야 하기 때문에 헤더에 자주 사용되며 컴파일러가 동일한 코드를 여러 번 재컴파일하고 나중에 중복 제거하도록 하는 것이 (프로그래머로서) 더 쉽습니다. . 템플릿은 코드를 직접 나타내는 것이 아니라 해당 코드의 여러 버전에 대한 템플릿임을 기억하십시오. .cpp
파일에서 템플릿이 아닌 함수를 컴파일하면 구체적인 함수/클래스를 컴파일하는 것입니다. 이것은 다른 유형으로 인스턴스화할 수 있는 템플릿의 경우가 아닙니다. 즉, 템플릿 매개변수를 구체적인 유형으로 바꿀 때 구체적인 코드를 내보내야 합니다.
별도의 컴파일에 사용되는 export
키워드 기능이 있었습니다. export
C++11
에서 더 이상 사용되지 않으며 AFAIK에서는 하나의 컴파일러만 이를 구현했습니다. export
사용하면 안됩니다. C++
또는 C++11
에서는 별도의 컴파일이 불가능 C++17
에서는 개념이 포함된다면 별도의 컴파일 방법이 있을 수 있습니다.
별도의 컴파일이 이루어지려면 별도의 템플릿 본문 확인이 가능해야 합니다. 개념으로 해결이 가능한 것 같습니다. 최근 표준 위원회 회의에서 발표된 이 문서를 살펴보십시오. 사용자 코드에서 템플릿 코드에 대한 코드를 인스턴스화해야 하기 때문에 이것이 유일한 요구 사항은 아니라고 생각합니다.
템플릿에 대한 별도의 컴파일 문제 현재 작업 중인 모듈로의 마이그레이션에서도 발생하는 문제인 것 같습니다.
편집: 2020년 8월 현재 모듈은 이미 C++의 현실입니다: https://en.cppreference.com/w/cpp/language/modules
위에 좋은 설명이 많이 있지만 템플릿을 머리글과 본문으로 분리하는 실용적인 방법을 놓치고 있습니다.
내 주요 관심사는 정의를 변경할 때 모든 템플릿 사용자의 재컴파일을 피하는 것입니다.
템플릿 본문에 모든 템플릿 인스턴스화를 갖는 것은 실행 가능한 솔루션이 아닙니다. 템플릿 작성자가 사용 방법과 템플릿 사용자가 템플릿을 수정할 수 있는 권한이 없을 수 있으므로 모든 것을 알지 못할 수 있기 때문입니다.
이전 컴파일러(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 >;
메인.cpp:
#include "MyInstantiatedTemplate.h" int main() { MyInstantiatedTemplate m(100); m.dump(); return 0; }
이렇게 하면 모든 템플릿 사용자(및 종속성)가 아닌 템플릿 인스턴스화만 다시 컴파일해야 합니다.
템플릿 클래스의 메소드 구현을 정의하는 가장 이식 가능한 방법은 템플릿 클래스 정의 내부에서 정의하는 것임을 의미합니다.
template < typename ... > class MyClass { int myMethod() { // Not just declaration. Add method implementation here } };
여기에 주목할만한 것을 추가하십시오. 함수 템플릿이 아닌 경우 구현 파일에서 템플릿 클래스의 메서드를 정의할 수 있습니다.
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 파일은 참조 또는 정의되지 않은 기호를 포함하는 순수 개체 또는 기계어 코드로 변환됩니다. 왜냐하면 main.cpp에 포함된 .h 파일에는 아직 구현이 없기 때문입니다. 템플릿에 대한 구현을 정의하는 다른 개체 파일과 연결할 준비가 되어 있으므로 완전한.out 실행 파일이 있습니다.
그러나 템플릿은 정의한 각 템플릿 인스턴스화에 대한 코드를 생성하기 위해 컴파일 단계에서 처리되어야 하므로 헤더 파일과 별도로 템플릿을 컴파일하는 것만으로는 작동하지 않습니다. 각 템플릿 인스턴스화는 말 그대로 완전히 새로운 클래스입니다. 일반 클래스에서는 .h와 .cpp를 분리할 수 있습니다. .h는 해당 클래스의 청사진이고 .cpp는 원시 구현이므로 모든 구현 파일을 정기적으로 컴파일하고 연결할 수 있지만 템플릿을 사용하면 .h가 클래스는 템플릿 .cpp 파일이 클래스의 원시 일반 구현이 아니라는 것을 의미하는 객체가 어떻게 보여야 하는지가 아니라 단순히 클래스에 대한 청사진이므로 .h 템플릿 파일의 구현은 컴파일될 수 없습니다. 컴파일하려면 구체적인 것이 필요하고 템플릿은 그런 의미에서 추상적입니다.
따라서 템플릿은 별도로 컴파일되지 않으며 다른 소스 파일에 구체적인 인스턴스가 있는 경우에만 컴파일됩니다. 그러나 구체적인 인스턴스화는 템플릿 파일의 구현을 알아야 합니다 typename T
를 수정하는 것만으로는 작업을 수행할 수 없습니다. 왜냐하면 링크할 .cpp가 있기 때문입니다. 템플릿은 추상적이고 컴파일할 수 없기 때문에 나중에 찾으십시오. 그래서 지금 당장 구현을 해야 합니다. 그래서 무엇을 컴파일하고 연결할지 알 수 있습니다. 파일. 기본적으로 템플릿을 인스턴스화하는 순간 완전히 새로운 클래스를 생성해야 하며, 컴파일러에게 알리지 않는 한 내가 제공한 유형을 사용할 때 해당 클래스가 어떻게 생겼는지 모른다면 그렇게 할 수 없습니다. 템플릿 구현이므로 이제 컴파일러는 T
를 내 유형으로 바꾸고 컴파일 및 연결할 준비가 된 구체적인 클래스를 만들 수 있습니다.
요약하자면 템플릿은 클래스가 어떻게 보여야 하는지에 대한 청사진이고 클래스는 객체가 어떻게 보여야 하는지에 대한 청사진입니다. 컴파일러가 구체적인 유형만 컴파일하기 때문에 템플릿을 구체적인 인스턴스화와 별도로 컴파일할 수 없습니다. 즉, 적어도 C++의 템플릿은 순수한 언어 추상화이기 때문입니다. 말하자면 템플릿을 추상화 해제해야 하며 템플릿 추상화가 일반 클래스 파일로 변환되고 차례로 정상적으로 컴파일될 수 있도록 처리할 구체적인 유형을 제공하여 수행합니다. 템플릿 .h 파일과 템플릿 .cpp 파일을 분리하는 것은 의미가 없습니다. .cpp와 .h의 분리는 .cpp가 개별적으로 컴파일될 수 있고 개별적으로 링크될 수 있는 경우에만 의미가 없습니다. 템플릿은 추상화이므로 템플릿을 별도로 컴파일할 수 없기 때문에 템플릿과 함께 항상 연결해야 합니다. 구체적인 인스턴스화가 항상 사용되는 유형에 대해 알아야 하는 경우 추상화를 항상 구체적인 인스턴스화와 함께 사용합니다.
의미 typename T
get은 연결 단계가 아닌 컴파일 단계에서 대체되므로 T
가 컴파일러에 완전히 의미가 없는 구체적인 값 유형으로 대체되지 않고 템플릿을 컴파일하려고 하면 결과적으로 개체 코드를 생성할 수 없기 때문에 T
가 무엇인지 모른다.
기술적으로 template.cpp 파일을 저장하고 다른 소스에서 찾을 때 유형을 전환하는 일종의 기능을 만드는 것이 가능합니다. 표준에는 템플릿을 넣을 수 export
별도의 cpp 파일이 있지만 실제로 구현하는 컴파일러는 많지 않습니다.
참고로, 템플릿 클래스에 대한 전문화를 만들 때 정의에 의한 전문화란 개별적으로 컴파일 및 링크될 수 있는 구체적인 유형을 전문화한다는 의미이므로 헤더를 구현에서 분리할 수 있습니다.
컴파일러는 할당을 위한 유형을 알아야 하기 때문에 정확히 맞습니다. 따라서 템플릿 클래스, 함수, 열거형 등은 헤더 파일이 공개되거나 라이브러리의 일부(정적 또는 동적)로 만들어지는 경우 헤더 파일에서도 구현되어야 합니다. 헤더 파일은 c/cpp 파일과 달리 컴파일되지 않기 때문입니다. 이다. 컴파일러가 유형을 알지 못하면 컴파일할 수 없습니다. .Net에서는 모든 객체가 Object 클래스에서 파생되기 때문에 가능합니다. 이것은 .Net이 아닙니다.
.h를 사용하는 모든 .cpp 모듈의 일부로 .h를 컴파일하여 생성되는 추가 컴파일 시간과 이진 크기 팽창이 우려되는 경우 많은 경우에 템플릿 클래스를 템플릿화되지 않은 기본 클래스에서 파생하도록 할 수 있습니다. 인터페이스의 유형 종속적이지 않은 부분과 해당 기본 클래스는 .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에는 정방향 선언이 있습니다. 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++ 표준 라이브러리 및 Boost 템플릿에 의존합니다. 대규모 템플릿 클래스의 경우 필요한 유형에 대해 한 번 수동 템플릿 인스턴스화를 수행합니다.
이것은 다른 프로그램에서 사용하기 위한 템플릿 라이브러리가 아닌 작업 프로그램을 제공하는 것이기 때문에 이것이 저의 접근 방식입니다. 이 책의 저자인 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]; }
출처 : http:www.stackoverflow.com/questions/495021/why-can-templates-only-be-implemented-in-the-header-file
forEach 루프와 함께 async/await 사용 (0) | 2021.12.06 |
---|---|
목록의 요소 수를 얻으려면 어떻게 해야 합니까? (0) | 2021.12.06 |
파일을 한 줄씩 목록으로 읽는 방법은 무엇입니까? (0) | 2021.12.06 |
SQL Server DateTime 데이터 유형에서 날짜만 반환하는 방법 (0) | 2021.12.04 |
Vim은 마지막 검색 강조 표시를 지웁니다. (0) | 2021.12.04 |