extern 템플릿 사용 (C ++ 11)


116

그림 1 : 기능 템플릿

TemplHeader.h

template<typename T>
void f();

TemplCpp.cpp

template<typename T>
void f(){
   //...
}    
//explicit instantation
template void f<T>();

Main.cpp

#include "TemplHeader.h"
extern template void f<T>(); //is this correct?
int main() {
    f<char>();
    return 0;
}

을 사용하는 올바른 방법입니까 extern template, 아니면 그림 2와 같이 클래스 템플릿에만이 키워드를 사용합니까?

그림 2 : 클래스 템플릿

TemplHeader.h

template<typename T>
class foo {
    T f();
};

TemplCpp.cpp

template<typename T>
void foo<T>::f() {
    //...
}
//explicit instantation
template class foo<int>;

Main.cpp

#include "TemplHeader.h"
extern template class foo<int>();
int main() {
    foo<int> test;
    return 0;
}

이 모든 것을 하나의 헤더 파일에 넣는 것이 좋지만 여러 파일에서 동일한 매개 변수로 템플릿을 인스턴스화하면 여러 개의 동일한 정의가 생기고 컴파일러는 오류를 피하기 위해 하나를 제외하고 모두 제거합니다. 어떻게 사용 extern template합니까? 클래스에만 사용할 수 있습니까, 아니면 함수에도 사용할 수 있습니까?

또한 그림 1과 그림 2는 템플릿이 단일 헤더 파일에있는 솔루션으로 확장 될 수 있습니다. 이 경우 extern template동일한 인스턴스가 여러 개 발생하지 않도록 키워드를 사용해야합니다 . 이것은 클래스 나 함수에만 해당됩니까?


3
이것은 extern 템플릿의 올바른 사용법이 아닙니다 ... 이것은 컴파일도하지 않습니다
Dani

(하나) 질문을 더 명확하게 표현하는 데 시간을 할애 할 수 있습니까? 무엇을 위해 코드를 게시하고 있습니까? 그와 관련된 질문이 없습니다. 또한 extern template class foo<int>();실수 인 것 같습니다.
sehe

1 경고 C4231 경고 : @Dani이>는 경고 메시지를 제외하고 내 비주얼 스튜디오 2010에 잘 컴파일 사용되는 비표준 확장을 : 템플릿 명시 적 인스턴스화하기 전에 '통근'
codekiddy

2
@sehe 질문은 매우 간단합니다. extern 템플릿 키워드를 사용하는 방법과시기는? (extern 템플릿은 C ++ 0x new future btw입니다) "또한 extern 템플릿 클래스 foo <int> (); 실수 인 것 같습니다."라고 말했습니다. 아니, 나는 새로운 C ++ 책을 가지고 있으며 그것은 내 책의 예입니다.
codekiddy 2011

1
@codekiddy : 그러면 Visual Studio는 정말 멍청합니다. 두 번째에서 프로토 타입은 구현과 일치하지 않으며 ()extern 라인 근처 에 'expected unqualified-id'라고 표시되어 있어도 수정합니다 . 책과 비주얼 스튜디오가 모두 잘못되었습니다. g ++ 또는 clang과 같은 표준 호환 컴파일러를 사용하면 문제가 발생합니다.
Dani

답변:


181

템플릿이 다른 곳에서 인스턴스화된다는 것을 알고 있을 때 extern template컴파일러가 템플릿을 인스턴스화 하지 않도록 강제하는 데 에만을 사용해야 합니다 . 컴파일 시간과 개체 파일 크기를 줄이는 데 사용됩니다.

예를 들면 :

// header.h

template<typename T>
void ReallyBigFunction()
{
    // Body
}

// source1.cpp

#include "header.h"
void something1()
{
    ReallyBigFunction<int>();
}

// source2.cpp

#include "header.h"
void something2()
{
    ReallyBigFunction<int>();
}

그러면 다음과 같은 개체 파일이 생성됩니다.

source1.o
    void something1()
    void ReallyBigFunction<int>()    // Compiled first time

source2.o
    void something2()
    void ReallyBigFunction<int>()    // Compiled second time

두 파일이 함께 링크 된 경우 하나 void ReallyBigFunction<int>()가 삭제되어 컴파일 시간과 개체 파일 크기가 낭비됩니다.

컴파일 시간과 객체 파일 크기를 낭비하지 않기 extern위해 컴파일러가 템플릿 함수를 컴파일하지 않도록 하는 키워드가 있습니다. 다른 곳에서 동일한 바이너리에서 사용 된다는 것을 알고있는 경우에만 이것을 사용해야합니다 .

다음으로 변경 source2.cpp:

// source2.cpp

#include "header.h"
extern template void ReallyBigFunction<int>();
void something2()
{
    ReallyBigFunction<int>();
}

다음과 같은 개체 파일이 생성됩니다.

source1.o
    void something1()
    void ReallyBigFunction<int>() // compiled just one time

source2.o
    void something2()
    // No ReallyBigFunction<int> here because of the extern

이 두 가지가 함께 연결될 때 두 번째 개체 파일은 첫 번째 개체 파일의 기호 만 사용합니다. 폐기 할 필요가 없으며 컴파일 시간과 개체 파일 크기를 낭비하지 않습니다.

이것은 프로젝트 내에서만 사용해야합니다. 예를 들어 템플릿을 vector<int>여러 번 사용할 때처럼 extern하나의 소스 파일을 제외하고 모두 사용해야 합니다.

이것은 클래스와 함수, 심지어 템플릿 멤버 함수에도 적용됩니다.


2
@codekiddy : 저는 Visual Studio가 의미하는 바를 전혀 모릅니다. 대부분의 C ++ 11 코드가 제대로 작동하기를 원한다면 더 호환되는 컴파일러를 사용해야합니다.
Dani

4
@Dani : 지금까지 읽은 extern 템플릿에 대한 최고의 설명!
Pietro

90
"다른 곳에서 동일한 바이너리에서 사용되는 것을 알고 있다면.". 그것은 충분하지도 필요하지도 않습니다. 귀하의 코드는 "잘못된 형식이며 진단이 필요하지 않습니다". 다른 TU의 암시 적 인스턴스화에 의존 할 수 없습니다 (컴파일러는 인라인 함수처럼이를 최적화 할 수 있습니다). 다른 TU에서 명시 적 인스턴스화를 제공해야합니다.
Johannes Schaub-litb

32
나는이 대답이 아마도 틀렸을 것임을 지적하고 싶습니다. 운 좋게도 Johannes의 댓글에는 많은 찬성표가 있었고 이번에는 더 많은 관심을 기울였습니다. 이 질문에 대한 대다수의 유권자들이 실제로 여러 컴파일 단위에서 이러한 유형의 템플릿을 구현하지 않았다고 가정 할 수 있습니다 (오늘처럼) ... 적어도 clang의 경우 유일한 확실한 방법은 이러한 템플릿 정의를 헤더! 경고 받다!
Steven Lu

6
@ JohannesSchaub-litb, 좀 더 자세히 설명하거나 더 나은 답변을 제공 할 수 있습니까? 당신의 반대 의견을 완전히 이해했는지 잘 모르겠습니다.
andreee

48

Wikipedia에 최고의 설명이 있습니다.

C ++ 03에서 컴파일러는 완전히 지정된 템플릿이 번역 단위에서 발견 될 때마다 템플릿을 인스턴스화해야합니다. 템플릿이 여러 번역 단위에서 동일한 유형으로 인스턴스화되면 컴파일 시간이 크게 늘어날 수 있습니다. C ++ 03에서는이를 방지 할 방법이 없으므로 C ++ 11은 extern 데이터 선언과 유사한 extern 템플릿 선언을 도입했습니다.

C ++ 03에는 컴파일러가 템플릿을 인스턴스화하도록하는 다음 구문이 있습니다.

  template class std::vector<MyClass>;

C ++ 11은 이제 다음 구문을 제공합니다.

  extern template class std::vector<MyClass>;

이 변환 단위에서 템플릿을 인스턴스화하지 않도록 컴파일러에 지시합니다.

경고 : nonstandard extension used...

Microsoft VC ++는 이미 몇 년 동안 이 기능의 비표준 버전 을 가지고 있었습니다 (C ++ 03에서). 컴파일러는 다른 컴파일러에서도 컴파일해야하는 코드의 이식성 문제를 방지하기 위해 경고합니다.

링크 된 페이지 의 샘플 을보고 거의 동일한 방식으로 작동하는지 확인하십시오. 물론 다른 비표준 컴파일러 확장을 동시에 사용할 때를 제외하고는 MSVC의 향후 버전에서 메시지가 사라질 것으로 예상 할 수 있습니다 .


tnx for your reply sehe, 그래서이 acctualy 의미는 "extern template"미래가 VS 2010에서 완전히 작동하고 경고를 무시할 수 있다는 것입니까? (예를 들어 pragma를 사용하여 메시지를 무시 함) 템플릿이 VSC ++에서 제 시간에 더 많이 인스턴스화되지 않음을 알리십시오. 컴파일러. 감사.
codekiddy 2011

4
"... 컴파일러에게이 번역 단위에서 템플릿을 인스턴스화하지 않도록 지시합니다 ." 나는 이것이 사실이라고 생각하지 않는다. 클래스 정의에 정의 된 모든 메서드는 인라인으로 계산되므로 STL 구현에서 인라인 메서드를 사용하는 경우 std::vector(모두 사용하는 것이 확실 함) extern효과가 없습니다.
Andreas Haferburg 2015

네,이 대답은 오해의 소지가 있습니다. MSFT doc : "전문화의 extern 키워드는 클래스 본문 외부에 정의 된 멤버 함수에만 적용됩니다. 클래스 선언 내부에 정의 된 함수는 인라인 함수로 간주되며 항상 인스턴스화됩니다." VS의 모든 STL 클래스 (마지막 확인은 2017 년)에는 안타깝게도 인라인 메서드 만 있습니다.
0kcats

발생 위치에 관계없이 모든 인라인 선언에 적용됩니다. 항상 @ 0kcats
sehe

@sehe std :: vector 예제를 사용하는 Wiki에 대한 참조와 동일한 답변에서 MSVC에 대한 참조는 MSVC에서 extern std :: vector를 사용하는 데 약간의 이점이있을 수 있지만 지금까지는 없습니다. 이것이 표준의 요구 사항인지 확실하지 않은 경우 다른 컴파일러에도 동일한 문제가있을 수 있습니다.
0kcats

7

extern template 템플릿 선언이 완료된 경우에만 필요합니다.

이것은 다른 답변에서 암시되었지만 충분히 강조되지 않은 것 같습니다.

이것이 의미하는 바는 OP의 예에서 extern template 에서 헤더의 템플릿 정의가 불완전하기 때문에 효과가 없다는 것입니다.

  • void f();: 선언, 본문 없음
  • class foo: 메소드 선언 f() 하지만 정의가 없습니다.

그래서 그냥 제거하는 것이 좋습니다 extern template 특정 경우에 정의를 . 클래스가 완전히 정의 된 경우에만 추가하면됩니다.

예를 들면 :

TemplHeader.h

template<typename T>
void f();

TemplCpp.cpp

template<typename T>
void f(){}

// Explicit instantiation for char.
template void f<char>();

Main.cpp

#include "TemplHeader.h"

// Commented out from OP code, has no effect.
// extern template void f<T>(); //is this correct?

int main() {
    f<char>();
    return 0;
}

다음을 사용하여 심볼 컴파일 및보기 nm:

g++ -std=c++11 -Wall -Wextra -pedantic -c -o TemplCpp.o TemplCpp.cpp
g++ -std=c++11 -Wall -Wextra -pedantic -c -o Main.o Main.cpp
g++ -std=c++11 -Wall -Wextra -pedantic -o Main.out Main.o TemplCpp.o
echo TemplCpp.o
nm -C TemplCpp.o | grep f
echo Main.o
nm -C Main.o | grep f

산출:

TemplCpp.o
0000000000000000 W void f<char>()
Main.o
                 U void f<char>()

그리고 man nm우리는 그것이 U정의되지 않음 을 의미 한다는 것을 알았습니다. 그래서 정의는TemplCpp 원하는 되었습니다.

이 모든 것은 완전한 헤더 선언의 절충으로 귀결됩니다.

  • 장점 :
    • 외부 코드에서 새로운 유형의 템플릿을 사용할 수 있습니다.
    • 객체 팽창에 문제가없는 경우 명시 적 인스턴스화를 추가하지 않는 옵션이 있습니다.
  • 단점 :
    • 해당 클래스를 개발할 때 헤더 구현 변경으로 인해 스마트 빌드 시스템이 많은 파일이 될 수있는 모든 포함자를 다시 빌드하게됩니다.
    • 객체 파일 팽창을 피하려면 명시 적 인스턴스화 (불완전한 헤더 선언과 동일)를 수행 할뿐만 아니라 extern template모든 포함자를 추가해야합니다.

추가 예는 다음에서 볼 수 있습니다. 명시 적 템플릿 인스턴스화-언제 사용됩니까?

컴파일 시간은 대규모 프로젝트에서 매우 중요하므로 외부 당사자가 자신의 복잡한 사용자 정의 클래스와 함께 코드를 절대적으로 재사용해야하는 경우가 아니라면 불완전한 템플릿 선언을 적극 권장합니다.

그리고이 경우 빌드 시간 문제를 피하기 위해 먼저 다형성을 사용하고 눈에 띄는 성능 향상을 얻을 수있는 경우에만 템플릿을 사용하려고합니다.

Ubuntu 18.04에서 테스트되었습니다.


4

템플릿의 알려진 문제는 코드 팽창이며, 이는 클래스 템플릿 전문화를 호출하는 모든 모듈에서 클래스 정의를 생성 한 결과입니다. 이를 방지하기 위해 C ++ 0x부터 시작 하여 클래스 템플릿 전문화 앞에 키워드 extern 을 사용할 수 있습니다.

#include <MyClass>
extern template class CMyClass<int>;

템플릿 클래스의 명시 적 인스턴스는 단일 번역 단위에서만 발생해야하며, 템플릿 정의 (MyClass.cpp)가있는 것이 더 좋습니다.

template class CMyClass<int>;
template class CMyClass<float>;

0

이전에 함수에 extern을 사용한 적이 있다면 템플릿에 대해서도 똑같은 철학을 따릅니다. 그렇지 않은 경우 간단한 기능을 위해 extern을 사용하면 도움이 될 수 있습니다. 또한 헤더 파일에 extern (s)을 넣고 필요할 때 헤더를 포함 할 수 있습니다.

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