code

템플릿 정적 변수

codestyles 2020. 12. 9. 08:12
반응형

템플릿 정적 변수


이해할 수 없습니다. 헤더에 일반적인 (비 템플릿) 클래스의 정적 변수를 정의하면 링커 오류가 발생하지만 템플릿의 경우 모두 정상적으로 작동하며 모든 번역 단위 중 정적 변수의 단일 인스턴스가있는 이유를 이해할 수 없습니다. :

템플릿 헤더 (template.h)입니다.

// template.h
template<typename T>
class Templ {
public:
  static int templStatic;
};

template<typename T> Templ<T>::templStatic = 0;

템플릿 (unit1.cpp)을 사용하는 첫 번째 유닛입니다.

// unit1.cpp
#include "template.h"

int method1() {
  return Templ<void>::templStatic++;
}

여기에 두 번째 단위 (unit2.cpp) :

// unit2.cpp
#include "template.h"
int method2() {
  return Templ<void>::templStatic++;
}

그리고 마지막으로 main.cpp :

// main.cpp
#include <iostream>
int method1();
int method2();

int main(int argc, char** argv) {
  std::cout << method1() << std::endl;
  std::cout << method2() << std::endl;
}

이 코드를 컴파일, 링크 및 실행하면 다음과 같은 출력이 나타납니다.

0
1

그렇다면 템플릿의 경우 모든 것이 잘 작동하는 이유는 무엇입니까 (예상대로)? 컴파일러 또는 링커가이를 처리하는 방법 (컴파일러를 개별적으로 호출하여 각 .cpp 파일을 컴파일 한 다음 caling을 사용하여 링커에 연결할 수 있으므로 컴파일러와 링커가 동시에 모든 .cpp 파일을 "볼"수 없습니다)?

추신 : 내 컴파일러 : msvcpp 9 (그러나 mingw에서도 확인)


정적 데이터 멤버의 정의 자체가 템플릿이기 때문입니다. 이를 허용하는 것은 프로그램에서 여러 번 인라인되지 않는 함수 템플릿을 가질 수있는 것과 같은 이유로 필요합니다. 결과 엔터티 (예 : 함수 또는 정적 데이터 멤버)를 생성하려면 템플릿이 필요합니다. 정적 데이터 멤버의 정의를 넣을 수없는 경우 다음을 어떻게 인스턴스화 할 수 있습니까?

template<typename T>
struct F {
  static int const value;
};

template<typename T>
int const F<T>::value = sizeof(T);

이것이 무엇인지 T알 수 없습니다 . 표준은 클래스 템플릿 외부의 정의가 템플릿 정의라고 말하며, 여기서 매개 변수는 클래스 템플릿 소유자로부터 상속됩니다.


GCC로 몇 가지 실험을했습니다. 다음에서는를 암시 적으로 인스턴스화 F<float>::value하고 F<char>::value여러 번 포함 할 때 중복 된 기호 오류를 일으키지 않도록 .cpp 파일에 정의되어야하는 명시 적 특수화가 하나 있습니다.

// Translation Unit 1
template<typename T>
struct F {
  static int value; 
};

template<typename T>
int F<T>::value = sizeof(T);

// this would belong into a .cpp file
template<> int F<char>::value = 2;

// this implicitly instantiates F<float>::value
int test = F<float>::value;

int main() { }

두 번째 변환 단위에는 동일한 정적 데이터 멤버의 또 다른 암시 적 인스턴스화 만 포함됩니다.

template<typename T>
struct F {
  static int value; 
};

template<typename T>
int F<T>::value = sizeof(T);

int test1 = F<float>::value;

다음은 GCC로 얻은 것입니다. 각 암시 적 인스턴스화를 약한 기호로 만들고 여기에 자체 섹션에 붙입니다. 약한 심볼은 링크 타임에 여러 심볼이있을 때 오류를 일으키지 않습니다. 대신 링커는 하나의 인스턴스를 선택하고 모든 인스턴스가 동일하다고 가정하여 다른 인스턴스를 버립니다.

objdump -Ct main1.o # =>
# cut down to the important ones
00000000 l    df *ABS*  00000000 main1.cpp
0000000a l     F .text  0000001e __static_initialization_and_destruction_0(int, int)
00000000 l    d  .data._ZN1FIfE5valueE  00000000 .data._ZN1FIfE5valueE
00000028 l     F .text  0000001c global constructors keyed to _ZN1FIcE5valueE
00000000 g     O .data  00000004 F<char>::value
00000000 g     O .bss   00000004 test
00000000 g     F .text  0000000a main
00000000  w    O .data._ZN1FIfE5valueE  00000004 F<float>::value

So as we can see F<float>::value is a weak symbol which means the linker can see multiple of these at link time. test, main and F<char>::value are global (non-weak) symbols. Linking main1.o and main2.o together, we see in the map output (-Wl,-M) the following

# (mangled name)
.data._ZN1FIfE5valueE
    0x080497ac        0x4 main1.o                                             
    0x080497ac                F<float>::value

This indicates that actually it drops all except one instance.


There is solution, you can create a parent class and put the static variable in it, then make your template class inherit it privately, here's an example:

class Parent
{
protected: 
    static long count;
};

long Parent::count = 0;

template<typename T>
class TemplateClass: private Parent
{
private: 
    int mKey;
public:
    TemplateClass():mKey(count++){}
    long getKey(){return mKey;}
}

int main()
{
    TemplateClass<int> obj1;
    TemplateClass<double> obj2;

    std::cout<<"Object 1 key is: "<<obj1.getKey()<<std::endl;
    std::cout<<"Object 2 key is: "<<obj2.getKey()<<std::endl;

    return 0;
}

Output will be:

Object 1 key is: 0 
Object 2 key is: 1

It's because template code is not source code; it's instructions on how to write source code.

The non-template static variable is actual source code, and the compiler will attempt to do exactly what you say by including something in twice. Hence, you have to initialize the static variable in a .cpp file, and only reference it in the .h file describing the class. It's equivalent to a global variable declared through extern.

When the compiler sees

template<class T> Templ{...};

it does nothing except make a note that the template exists. As far as it is concerned, there is no source code associated with Templ. The first time you actually refer to

Templ<int> Instance

the compiler looks at all the template<> code associated with Templ and uses it to construct a .h and a .cpp file (which exists only for the duration of compilation). Those files might look like this:

Temple_int.h
class Templ_int{
  public:
  static int templStatic;
};

Templ_int.cpp
#include "Templ_int.h"
Templ_int::templStatic = 0;

And every

Templ<int>

becomes a Templ_int. Thus, the source code to initialize the static variable only exists once, in a .cpp file created by the compiler. (Obviously, the actual compiler-specific implementation of this process would be robust against creating a class with a similar name to the template, etc.)

참고URL : https://stackoverflow.com/questions/1553854/template-static-variable

반응형