C++ 模板静态变量

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1553854/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 20:24:55  来源:igfitidea点击:

Template static variable

c++templatesstatic

提问by cybevnm

I can't understand, why if we define static variable of usual (non-template) class in header, we have linker error, but in case of templates all works fine and moreover we will have single instance of static variable among all translation units:

我无法理解,为什么如果我们在标题中定义通常(非模板)类的静态变量,我们会出现链接器错误,但是在模板的情况下一切正常,而且我们将在所有翻译单元中拥有静态变量的单个实例:

It's template header (template.h):

它是模板头文件(template.h):

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

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

It's first unit using template (unit1.cpp)

它是第一个使用模板的单元(unit1.cpp)

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

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

Second unit here (unit2.cpp):

这里的第二个单元(unit2.cpp):

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

And, finally, main.cpp:

最后,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;
}

After compilling, linking and executing this code, we will have following output:

编译、链接和执行此代码后,我们将得到以下输出:

0
1

So, why in case of templates all works fine (and as expected) ? How compiler or linker handle this (we can compile each .cpp file in separated calling of compiler, and then link them with caling to linker, so compiler and linker don't "see" all .cpp files at same time) ?

那么,为什么在模板的情况下一切正常(并按预期)?编译器或链接器如何处理这个(我们可以在单独的编译器调用中编译每个 .cpp 文件,然后将它们链接到链接器,因此编译器和链接器不会同时“看到”所有 .cpp 文件)?

PS: My compiler: msvcpp 9 (but checked on mingw too)

PS:我的编译器:msvcpp 9(但也检查了 mingw)

回答by Johannes Schaub - litb

It's because the definition of the static data member is itself a template. Allowing this is necessary for the same reason you are allowed to have a function template that's not inline multiple times in a program. You need the template to generate the resulting entity (say, a function, or a static data member). If you wouldn't be allowed to put the definition of a static data member, how would you instantiate the following

这是因为静态数据成员的定义本身就是一个模板。允许这样做是必要的,原因与允许在程序中多次内联的函数模板相同。您需要模板来生成结果实体(例如,函数或静态数据成员)。如果不允许您放置静态数据成员的定义,您将如何实例化以下内容

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

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

It's not known what Tis - the Standard says the definition outside the class template is a template definition, in which the parameters are inherited from its class template owner.

不知道是什么T- 标准说类模板之外的定义是模板定义,其中参数是从其类模板所有者继承的。



I've made some experiment with GCC. In the following, we have one implicit instantiation of F<float>::value, and one explicit specialization of F<char>::valuewhich has to be defined in a .cpp file to not cause duplicated symbol errors when included multiple times.

我用 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() { }

The second translation unit contains just another implicit instantiation of the same static data member

第二个翻译单元只包含同一个静态数据成员的另一个隐式实例

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

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

int test1 = F<float>::value;

Here is what we get with GCC - it makes each implicit instantiation into a weak symbols and sticks it into its own section here. Weak symbols will not cause errors when there exist multiple of them at link time. Instead, the linker will choose one instance, and discards the other ones assuming all of them are the same

这是我们使用 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>::valueis a weak symbol which means the linker can see multiple of these at link time. test, mainand F<char>::valueare global (non-weak) symbols. Linking main1.oand main2.otogether, we see in the map output (-Wl,-M) the following

所以我们可以看到F<float>::value是一个弱符号,这意味着链接器可以在链接时看到多个这样的符号。test,mainF<char>::value是全局(非弱)符号。链接main1.omain2.o在一起,我们在地图输出中看到(-Wl,-M)以下

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

This indicates that actually it drops all except one instance.

这表明它实际上丢弃了除一个实例之外的所有实例。

回答by POSTHUMAN

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

回答by user1167758

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.

非模板静态变量是实际的源代码,编译器将尝试通过两次包含某些内容来完全按照您所说的去做。因此,您必须在 .cpp 文件中初始化静态变量,并且仅在描述类的 .h 文件中引用它。它相当于一个通过 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 相关的源代码。你第一次真正提到

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:

编译器查看与 Templ 关联的所有模板<>代码,并使用它来构造一个 .h 和一个 .cpp 文件(仅在编译期间存在)。这些文件可能如下所示:

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.)

变成一个 Templ_int。因此,初始化静态变量的源代码只存在一次,在编译器创建的 .cpp 文件中。(显然,此过程的实际编译器特定实现对于创建与模板等名称相似的类是健壮的。)