【问题标题】:Template static variable模板静态变量
【发布时间】:2010-12-05 22:44:23
【问题描述】:

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

是模板头(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 文件)?

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

【问题讨论】:

  • 如果您向我们展示不起作用的代码会更有用。
  • 我认为不起作用的代码是您在标头中定义变量的代码,该变量包含在多个文件(未外部)中,这会导致命名冲突。跨度>

标签: c++ templates static


【解决方案1】:

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

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

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

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


我用 GCC 做了一些实验。在下文中,我们有F&lt;float&gt;::value 的一个隐式实例化和F&lt;char&gt;::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

所以我们可以看到F&lt;float&gt;::value 是一个弱符号,这意味着链接器可以在链接时看到其中的多个。 testmainF&lt;char&gt;::value 是全局(非弱)符号。将 main1.omain2.o 链接在一起,我们在地图输出 (-Wl,-M) 中看到以下内容

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

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

【讨论】:

  • 好的。但是如何链接器,其中看到两个“template Templ::templStatic = 0;”定义(在 unit1.cpp 和 unit2.cpp 中)处理这种情况?目标文件是否有一些 C++ 特定的元信息,可以告诉链接器,可以忽略一个定义(因此我们没有“多个定义”链接器错误)?
【解决方案2】:

有解决办法,你可以创建一个父类,把静态变量放在里面,然后让你的模板类私有继承,这里是一个例子:

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;
}

输出将是:

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

【讨论】:

  • 您应该在回复中明确说明,所有 TemplateClass 实例共享 same 静态 count。因为您的答案与接受的答案中的模板化静态成员变量不同:在这种情况下,静态成员是每个模板参数共享的(即具有相同模板类型的模板的每个实例化)
  • @Ichthyo 这是预期的行为,所以我认为没有必要说清楚?
【解决方案3】:

这是因为模板代码不是源代码;这是关于如何编写源代码的说明。

非模板静态变量是实际的源代码,编译器将尝试通过两次包含某些内容来完全按照您所说的去做。因此,您必须在 .cpp 文件中初始化静态变量,并且仅在描述类的 .h 文件中引用它。相当于通过extern声明的全局变量。

当编译器看到

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

它除了记下模板存在之外什么都不做。就它而言,没有与 Templ 相关的源代码。 你第一次真正提到

Templ<int> Instance

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

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

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

每一个

Templ<int>

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

【讨论】:

  • 如果我们不初始化静态变量,为什么编译器会抛出错误。我试图删除以下行Templ_int::templStatic = 0;
猜你喜欢
  • 2019-05-10
  • 1970-01-01
  • 2010-10-11
  • 2010-11-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多