.CPP 파일에 C ++ 템플릿 함수 정의 저장


526

헤더에 인라인 대신 CPP 파일에 저장하려는 템플릿 코드가 있습니다. 어떤 템플릿 유형이 사용 될지 알기 만하면 이것이 가능하다는 것을 알고 있습니다. 예를 들면 다음과 같습니다.

.h 파일

class foo
{
public:
    template <typename T>
    void do(const T& t);
};

.cpp 파일

template <typename T>
void foo::do(const T& t)
{
    // Do something with t
}

template void foo::do<int>(const int&);
template void foo::do<std::string>(const std::string&);

마지막 두 줄-foo :: do 템플릿 함수는 ints 및 std :: strings에만 사용되므로 이러한 정의는 앱이 링크됨을 의미합니다.

내 질문은-이것은 불쾌한 해킹입니까 아니면 다른 컴파일러 / 링커와 함께 작동합니까? 현재이 코드를 VS2008에서만 사용하고 있지만 다른 환경으로 이식하려고합니다.


22
나는 이것이 가능한지 몰랐다 – 재미있는 속임수! 최근에해야 할 일이이 사실을 아는 데 도움이되었을 것입니다.
xan

69
나를 놀라게하는 것은 do식별자로 사용하는 것입니다 : p
Quentin

나는 gcc와 비슷한 소싱을 해왔지만 여전히 연구하고있다
Nick

16
이것은 "해킹"이 아니며 앞으로의 거부입니다. 여기에는 언어의 표준이 있습니다. 예, 모든 표준 준수 컴파일러에서 허용됩니다.
Ahmet Ipkin

1
수십 가지 방법이 있다면 어떨까요? template class foo<int>;template class foo<std::string>;.cpp 파일의 끝에서 할 수 있습니까 ?
무시

답변:


231

설명하는 문제는 헤더에서 템플릿을 정의하거나 위에서 설명한 접근 방식을 통해 해결할 수 있습니다.

C ++ FAQ Lite 에서 다음 사항을 읽는 것이 좋습니다 .

이러한 (및 기타) 템플릿 문제에 대해 자세히 설명합니다.


39
답변을 보완하기 위해 참조 링크는 질문에 긍정적으로 답변합니다. 즉 Rob이 제안한 것을 수행하고 코드를 이식 가능하게 만들 수 있습니다.
ivotron

160
답변 자체에 관련 부분을 게시 할 수 있습니까? 왜 그런 참조가 SO에서도 허용됩니까? 나는이 링크에서 무엇을 찾아야할지 전혀 알지 못합니다.
Ident

124

이 페이지의 다른 사람들은 명시 적 템플릿 전문화 (또는 적어도 VS2008에서)에 대한 올바른 구문이 무엇인지 궁금합니다.

.h 파일에서 ...

template<typename T>
class foo
{
public:
    void bar(const T &t);
};

그리고 .cpp 파일에서

template <class T>
void foo<T>::bar(const T &t)
{ }

// Explicit template instantiation
template class foo<int>;

15
"명시적인 CLASS 템플릿 전문화"를 의미합니까? 이 경우 템플릿 클래스가 가진 모든 함수를 다룰 것입니까?
Arthur

@Arthur는 보이지 않습니다. 일부 템플릿 메소드는 헤더에 있고 대부분의 다른 메소드는 cpp에서 잘 작동합니다. 아주 좋은 해결책.
user1633272

asker의 경우 클래스 템플릿이 아닌 함수 템플릿이 있습니다.
user253751

23

이 코드는 올바른 형식입니다. 템플릿 정의는 인스턴스화 시점에서 볼 수 있다는 점에만주의해야합니다. 표준을 인용하기 위해 § 14.7.2.4 :

익스포트되지 않은 함수 템플리트, 익스포트되지 않은 멤버 함수 템플리트 또는 익스포트되지 않은 멤버 함수 또는 클래스 템플리트의 정적 데이터 멤버의 정의는 명시 적으로 인스턴스화 된 모든 변환 단위에 있어야합니다.


2
무엇 않는 비는 - 수출 평균?
Dan Nissenbaum 2018 년

1
@Dan Visible은 컴파일 유닛 내부에서만 가능합니다. 여러 컴파일 단위를 함께 연결하는 경우 내 보낸 심볼을 여러 심볼 단위에서 사용할 수 있습니다 (템플릿의 경우 일관성있는 정의가 하나 이상이어야합니다. 그렇지 않으면 UB가 발생 함).
Konrad Rudolph

감사. 모든 기능은 기본적으로 컴파일 장치 외부에서 볼 수 있다고 생각했습니다. 두 개의 컴파일 단위 a.cpp(함수 정의 a() {})와 b.cpp(함수 정의 b() { a() })가 있으면 성공적으로 연결됩니다. 내가 옳다면 위의 인용문은 전형적인 경우에는 적용되지 않는 것 같습니다 ... 어딘가에 잘못 가고 있습니까?
Dan Nissenbaum 2018 년

@ inline
Konrad Rudolph

1
@Dan 함수 템플릿은 암시 적으로 inline있습니다. 표준화 된 C ++ ABI가 없다면 이것이 다른 효과를 정의하기가 어렵거나 불가능하기 때문입니다.
Konrad Rudolph

15

템플릿이 지원되는 모든 곳에서 잘 작동합니다. 명시 적 템플릿 인스턴스화는 C ++ 표준의 일부입니다.


13

귀하의 예는 정확하지만 이식성이 떨어집니다. @ namespace-sid가 지적한대로 사용할 수있는 약간 더 깔끔한 구문도 있습니다.

템플릿 클래스가 공유 할 라이브러리의 일부라고 가정합니다. 템플릿 화 된 클래스의 다른 버전을 컴파일해야합니까? 라이브러리 관리자는 클래스의 모든 템플릿 사용을 예상해야합니까?

대체 방법은 사용자가 가지고있는 약간의 변형입니다. 템플릿 구현 / 인스턴스 파일 인 세 번째 파일을 추가하십시오.

foo.h 파일

// Standard header file guards omitted

template <typename T>
class foo
{
public:
    void bar(const T& t);
};

foo.cpp 파일

// Always include your headers
#include "foo.h"

template <typename T>
void foo::bar(const T& t)
{
    // Do something with t
}

foo-impl.cpp 파일

// Yes, we include the .cpp file
#include "foo.cpp"
template class foo<int>;

한 가지주의해야 할 점은 컴파일하는 컴파일러 말할 필요가있다 foo-impl.cpp대신 foo.cpp후자는 아무것도하지 않는 컴파일 등을.

물론 세 번째 파일에 여러 구현을 사용하거나 사용하려는 각 유형에 대해 여러 구현 파일을 가질 수 있습니다.

이를 통해 다른 용도로 템플릿 클래스를 공유 할 때 훨씬 더 유연합니다.

이 설정은 또한 각 번역 단위에서 동일한 헤더 파일을 다시 컴파일하지 않기 때문에 재사용 된 클래스의 컴파일 시간을 줄입니다.


이것은 무엇을 사나요? 새로운 전문화를 추가하려면 여전히 foo-impl.cpp를 편집해야합니다.
MK.

foo.cpp버전이 실제로 컴파일되는 (in foo-impl.cpp) 선언 (in foo.h) 인 구현 세부 사항 (일명 )을 분리 합니다. 나는 대부분의 C ++ 템플릿이 전적으로 헤더 파일로 정의되는 것을 싫어합니다. 이는 c[pp]/h각 클래스 / 네임 스페이스 / 사용하는 그룹화 에 대한 C / C ++ 표준 쌍에 대응합니다. 사람들은 여전히이 대안이 널리 사용되거나 알려지지 않았기 때문에 여전히 모 놀리 식 헤더 파일을 사용하는 것 같습니다.
Cameron Tacklind

1
@MK. 다른 곳에서 추가 인스턴스화가 필요할 때까지 소스 파일의 정의 끝에 명시 적 템플릿 인스턴스화를 배치했습니다 (예 : 템플릿 유형으로 모의를 사용하는 단위 테스트). 이 분리를 통해 외부에서 더 많은 인스턴스를 추가 할 수 있습니다. 또한 h/cppinclude 가드에서 원래 인스턴스화 목록을 둘러싸 야했지만 원본을 쌍 으로 유지하면 여전히 작동 하지만 여전히 foo.cpp정상적으로 컴파일 할 수 있습니다 . 나는 여전히 C ++에 익숙하지 않으며이 혼합 사용에 추가주의 사항이 있는지 알고 싶습니다.
Thirdwater

3
나는 그것을 분리하는 것이 바람직하다고 생각 foo.cpp하고 foo-impl.cpp. 하지 마십시오 #include "foo.cpp"에서 foo-impl.cpp파일; 대신 컴파일 할 때 컴파일러가 템플릿을 인스턴스화하지 못하도록 선언 extern template class foo<int>;을 추가하십시오 . 빌드 시스템이 두 파일을 모두 빌드하고 두 오브젝트 파일을 모두 링커에 전달 하는지 확인하십시오 . 여기에는 여러 가지 이점이 있습니다. a) 인스턴스화가 없다는 것이 분명합니다 . b) foo.cpp에 대한 변경은 foo-impl.cpp의 재 컴파일을 요구하지 않습니다. foo.cppfoo.cpp.cppfoo.cpp
Shmuel Levine

3
이것은 헤더 구현과 자주 사용되는 유형의 인스턴스화 모두에서 가장 잘 사용되는 템플릿 정의 문제에 대한 매우 좋은 접근 방식입니다. 나는이 설정을 만들 것이다 유일한 변화는 이름을 변경하는 것입니다 foo.cpp으로 foo_impl.h하고 foo-impl.cpp단지로 foo.cpp. 또한 인스턴스화를 foo.cpp위해 typedef를 ~에서 foo.h와 같이 추가 using foo_int = foo<int>;합니다. 트릭은 사용자에게 두 가지 헤더 인터페이스를 제공하는 것입니다. 사용자가 사전 정의 된 인스턴스화 foo.h가 필요한 경우 사용자가 포함 된 순서가 잘못된 것이 필요할 때 포함 foo_impl.h합니다.
Wormer

5

이것은 분명 해킹은 아니지만 주어진 템플릿에 사용하려는 모든 클래스 / 유형에 대해 명시 적으로 템플릿을 지정해야한다는 사실을 알고 있어야합니다. 템플릿 인스턴스화를 요청하는 많은 유형의 경우 .cpp 파일에 많은 줄이있을 수 있습니다. 이 문제를 해결하려면 사용하는 모든 프로젝트에서 TemplateClassInst.cpp를 사용하여 인스턴스화 할 유형을보다 잘 제어 할 수 있습니다. 분명히이 솔루션은 ODR을 깨뜨릴 수 있으므로 완벽하지는 않습니다 (일명 은탄).


그것이 ODR을 깨뜨릴 것이라고 확신합니까? TemplateClassInst.cpp의 인스턴스화 라인이 동일한 소스 파일 (템플릿 함수 정의 포함)을 참조하는 경우 모든 정의가 동일하기 때문에 (반복하더라도) ODR을 위반하지 않는 것이 보장되지 않습니까?
Dan Nissenbaum 2018 년

ODR이란 무엇입니까?
이동할 수없는

4

최신 표준에는 export이 문제를 완화하는 데 도움이 되는 키워드 ( )가 있지만 Comeau 이외의 다른 컴파일러에서는 구현되지 않습니다.

이에 대한 FAQ를 참조하십시오 .


2
AFAIK, 수출은 새로운 문제에 직면하여 마지막 문제가 해결 될 때마다 전체 솔루션이 더욱 복잡해져 수출이 중단되었습니다. "export"키워드를 사용하면 CPP에서 "수출"할 수 없습니다 (여전히 H. Sutter의 경우). 그래서 나는 말합니다 : 숨을
참지

2
내보내기를 구현하려면 여전히 전체 템플릿 정의가 필요합니다. 당신이 얻는 모든 것은 그것을 일종의 컴파일 된 형태로 얻는 것입니다. 그러나 실제로는 아무런 의미가 없습니다.
Zan Lynx

2
... 최소 이득을위한 과도한 합병증으로 인해 표준에서 벗어 났습니다 .
DevSolar

4

이것이 템플릿 함수를 정의하는 표준 방법입니다. 템플릿을 정의하기 위해 읽은 세 가지 방법이 있다고 생각합니다. 또는 아마도 4. 각각의 장단점이 있습니다.

  1. 클래스 정의에서 정의하십시오. 클래스 정의는 엄격하게 참조하기 쉽고 읽기 쉬워야한다고 생각하기 때문에 전혀 마음에 들지 않습니다. 그러나 외부보다 클래스에서 템플릿을 정의하는 것이 훨씬 까다 롭지 않습니다. 모든 템플릿 선언이 동일한 수준의 복잡성에있는 것은 아닙니다. 이 방법은 또한 템플릿을 실제 템플릿으로 만듭니다.

  2. 같은 헤더에서 클래스 외부에 템플리트를 정의하십시오. 이것은 대부분 내가 선호하는 방법입니다. 클래스 정의를 깔끔하게 유지하고 템플릿은 실제 템플릿으로 유지됩니다. 그러나 까다로운 전체 템플릿 이름 지정이 필요합니다. 또한 코드는 모든 사람이 사용할 수 있습니다. 그러나 코드를 인라인으로 사용해야하는 경우 이것이 유일한 방법입니다. 클래스 정의 끝에 .INL 파일을 작성하여이를 수행 할 수도 있습니다.

  3. main.CPP에 header.h 및 implementation.CPP를 포함하십시오. 그렇게 된 것 같아요. 사전 인스턴스화를 준비하지 않아도 실제 템플릿처럼 작동합니다. 내가 가진 문제는 자연스럽지 않다는 것입니다. 우리는 일반적으로 소스 파일을 포함하지 않으며 포함 할 것으로 예상합니다. 소스 파일을 포함했기 때문에 템플릿 기능을 인라인 할 수 있습니다.

  4. 게시 된 방식 인이 마지막 방법은 번호 3과 마찬가지로 소스 파일에 템플릿을 정의하는 것입니다. 그러나 소스 파일을 포함하는 대신 템플릿을 필요한 템플릿으로 미리 인스턴스화합니다. 이 방법에는 문제가 없으며 때로는 편리합니다. 우리는 하나의 큰 코드를 가지고 있는데, 인라인으로 얻을 수 없으므로 CPP 파일에 넣으십시오. 그리고 일반적인 인스턴스화를 알고 있으면 미리 정의 할 수 있습니다. 이렇게하면 기본적으로 같은 내용을 5, 10 번 쓰지 않아도됩니다. 이 방법은 코드를 독점적으로 유지하는 이점이 있습니다. 그러나 정기적으로 사용되는 작은 기능을 CPP 파일에 넣지 않는 것이 좋습니다. 이렇게하면 라이브러리의 성능이 저하됩니다.

bloated obj 파일의 결과를 알지 못합니다.


3

예, 이것이 전문화 명시 적 인스턴스화 를 수행하는 표준 방법 입니다. 언급했듯이이 템플릿을 다른 유형으로 인스턴스화 할 수 없습니다.

편집 : 의견에 따라 수정되었습니다.


용어에 대해 까다롭기 때문에 "명시 적 인스턴스화"입니다.
Richard Corden

2

한 가지 예를 들어 보겠습니다. 어떤 이유로 템플릿 클래스를 원한다고 가정 해 봅시다.

//test_template.h:
#pragma once
#include <cstdio>

template <class T>
class DemoT
{
public:
    void test()
    {
        printf("ok\n");
    }
};

template <>
void DemoT<int>::test()
{
    printf("int test (int)\n");
}


template <>
void DemoT<bool>::test()
{
    printf("int test (bool)\n");
}

이 코드를 Visual Studio로 컴파일하면 즉시 작동합니다. gcc는 링커 오류를 생성합니다 (여러 헤더 파일이 여러 .cpp 파일에서 사용되는 경우).

error : multiple definition of `DemoT<int>::test()'; your.o: .../test_template.h:16: first defined here

구현을 .cpp 파일로 옮길 수는 있지만 다음과 같이 클래스를 선언해야합니다.

//test_template.h:
#pragma once
#include <cstdio>

template <class T>
class DemoT
{
public:
    void test()
    {
        printf("ok\n");
    }
};

template <>
void DemoT<int>::test();

template <>
void DemoT<bool>::test();

// Instantiate parametrized template classes, implementation resides on .cpp side.
template class DemoT<bool>;
template class DemoT<int>;

그리고 .cpp는 다음과 같습니다 :

//test_template.cpp:
#include "test_template.h"

template <>
void DemoT<int>::test()
{
    printf("int test (int)\n");
}


template <>
void DemoT<bool>::test()
{
    printf("int test (bool)\n");
}

헤더 파일에 마지막 두 줄이 없으면 gcc는 정상적으로 작동하지만 Visual Studio에서 오류가 발생합니다.

 error LNK2019: unresolved external symbol "public: void __cdecl DemoT<int>::test(void)" (?test@?$DemoT@H@@QEAAXXZ) referenced in function

.dll 내보내기를 통해 함수를 노출하려는 경우 템플릿 클래스 구문은 선택 사항이지만 Windows 플랫폼에만 적용 가능하므로 test_template.h는 다음과 같습니다.

//test_template.h:
#pragma once
#include <cstdio>

template <class T>
class DemoT
{
public:
    void test()
    {
        printf("ok\n");
    }
};

#ifdef _WIN32
    #define DLL_EXPORT __declspec(dllexport) 
#else
    #define DLL_EXPORT
#endif

template <>
void DLL_EXPORT DemoT<int>::test();

template <>
void DLL_EXPORT DemoT<bool>::test();

이전 예제의 .cpp 파일로.

그러나 링커에 더 많은 두통이 발생하므로 .dll 함수를 내 보내지 않으면 이전 예제를 사용하는 것이 좋습니다.


1

업데이트 할 시간입니다! 인라인 (.inl 또는 다른 파일) 파일을 만들고 모든 정의를 복사하십시오. 각 기능 위에 템플릿을 추가해야합니다 ( template <typename T, ...>). 이제 인라인 파일에 헤더 파일을 포함시키는 대신 반대로 수행하십시오. 클래스 선언 인라인 파일 포함하십시오 ( #include "file.inl").

왜 아무도 이것을 언급하지 않았는지 모르겠습니다. 즉각적인 단점은 없습니다.


25
즉각적인 단점은 헤더에서 템플릿 기능을 직접 정의하는 것과 근본적으로 동일하다는 것입니다. 일단 #include "file.inl"전처리 기가 내용을 file.inl헤더 에 직접 붙여 넣습니다 . 구현이 헤더로 들어가는 것을 피하려는 이유가 무엇이든이 솔루션은 그 문제를 해결하지 못합니다.
코디 그레이

5
- 그리고 당신은 기술적으로 불필요하게, 라인 외부 template정의에 필요한 모든 장황하고 마음이 구부러진 상용구를 작성하는 작업에 부담을지게 됩니다. 나는 사람들이 그것을하고 싶어하는 이유를 얻습니다. 템플릿이 아닌 선언 / 정의로 최대한의 패리티를 달성하고 인터페이스 선언을 깔끔하게 보이도록 유지하는 등 항상 번거롭지 않습니다. 양측의 트레이드 오프를 평가하고 가장 나쁜 것을 선택하는 경우입니다 . ... namespace class일이 될 때까지 : O [ Please please a thing ]
underscore_d

2
@Andrew 나는 의도적이지 않다고 말하는 누군가를 본 것 같지만위원회의 파이프에 붙어있는 것 같습니다. 나는 그것이 C ++ 17로 만들어 졌으면 좋겠다. 아마 다음 10 년.
underscore_d

@CodyGray : 기술적으로 이것은 컴파일러와 동일하므로 컴파일 시간을 단축하지 않습니다. 아직도 나는 이것이 내가 본 많은 프로젝트에서 언급하고 실천할 가치가 있다고 생각합니다. 이 길을 내려 가면 인터페이스와 정의를 분리하는 데 도움이되므로 좋은 방법입니다. 이 경우 ABI 호환성 등에 도움이되지 않지만 인터페이스를 읽고 이해하기가 쉽습니다.
kiloalphaindia 9

0

당신이 제시 한 예에는 아무런 문제가 없습니다. 그러나 함수 정의를 cpp 파일에 저장하는 것이 비효율적이라고 생각해야합니다. 나는 함수의 선언과 정의를 분리해야한다는 것을 이해합니다.

BCCL (부스트 개념 검사 라이브러리)을 명시 적 클래스 인스턴스화와 함께 사용하면 cpp 파일에서 템플릿 함수 코드를 생성 할 수 있습니다.


8
그것에 대해 비효율적 인 것은 무엇입니까?
코디 그레이

0

위의 어느 것도 나를 위해 일하지 않았으므로 여기에 어떻게 해결했는지, 내 클래스에는 템플릿이 하나만 있습니다.

.h

class Model
{
    template <class T>
    void build(T* b, uint32_t number);
};

.cpp

#include "Model.h"
template <class T>
void Model::build(T* b, uint32_t number)
{
    //implementation
}

void TemporaryFunction()
{
    Model m;
    m.build<B1>(new B1(),1);
    m.build<B2>(new B2(), 1);
    m.build<B3>(new B3(), 1);
}

이렇게하면 링커 오류가 발생하지 않으며 TemporaryFunction을 전혀 호출 할 필요가 없습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.