模板函数内的静态变量

时间:2009-06-15 02:53:56

标签: c++ templates static

在C ++中,如果在header.hpp中定义此函数

void incAndShow()
{
  static int myStaticVar = 0;
  std::cout << ++myStaticVar << " " << std::endl;
}

并且在至少两个.cpp文件中包含header.hpp。然后你将multiple definition of incAndShow()。这是预期的。但是,如果您将模板添加到函数

template <class T>
void incAndShow()
{
  static int myStaticVar = 0;
  std::cout << ++myStaticVar << " " << std::endl;
}

那么你就不会有任何multiple definition of错误。同样,使用相同模板(例如incAndShow<int>())调用函数的两个不同的.cpp将共享myStaticVar。这是正常的吗?我问这个问题,因为我确实依赖这个“功能”(共享静态变量),我想确保不仅仅是我的实现正在这样做。

7 个答案:

答案 0 :(得分:29)

你可以依靠这个。 ODR(一个定义规则)在标准3.2/5处说,其中D代表非静态函数模板(由我提供草书字体)

  

如果D是模板,并且是在多个翻译单元中定义的,那么上面列表中的最后四个要求将应用于模板定义(14.6.3)中使用的模板封闭范围中的名称,以及在实例化时的依赖名称(14.6.2)。 如果D的定义满足所有这些要求,则程序应该表现得就像D的单个定义一样。如果D的定义不满足这些要求,那么行为是不确定的。< / p>

在最后四项要求中,最重要的两项大致是

  • D的每个定义应由相同的令牌序列组成
  • 每个定义中的名称应指相同的事物(“实体”)

修改

我认为仅凭这一点并不足以保证不同实例中的静态变量都是相同的。以上仅保证模板的多个定义有效。它没有说明由它产生的特化。

这是链接开始的地方。如果函数模板特化(这是一个函数)的名称有外部链接(3.5/4),那么引用这样的名称专业化指的是同一个功能。对于声明为static的模板,由于

,从中实例化的函数具有内部链接
  

从具有内部链接的模板生成的实体与在其他翻译单元中生成的所有实体不同。 -- 14/4

     

具有命名空间范围(3.3.6)的名称具有内部链接,如果它是显式声明为静态-- 3.5/3

的对象,引用,函数或函数模板的名称

如果函数模板未使用static声明,那么它具有extern链接(顺便说一下,这也是我们必须遵循ODR的原因。否则,D将不会多次定义!)。这可以从14/4(与3.5/3

一起推导出来
  

非成员函数模板可以具有内部链接;任何其他模板名称都应具有外部链接。 -- 14/4

最后,我们得出结论,从具有外部链接的函数模板生成的函数模板特化通过3.5/4本身具有外部链接:

  

具有命名空间范围的名称具有外部链接,如果它是[...]函数的名称,除非它具有内部链接-- 3.5/4

当内部链接由3.5/3解释为显式特化提供的函数时,14/4解释生成的特化(模板实例化)。由于您的模板名称具有外部链接,因此您的所有专业化都具有外部链接:如果您使用来自不同翻译单元的名称(incAndShow<T>),则它们将引用相同的函数,这意味着您的静态对象将是相同的每个场合。

答案 1 :(得分:6)

我理解你的问题。您要问的是,模板化函数的每个版本都有自己的myStaticVar实例是否正常。 (例如:incAndShow<int>intAndShow<float>答案是肯定的。

你的另一个问题是,如果两个文件包含包含模板函数的标题,它们是否仍会共享给定T的静态变量。我会说是。

答案 2 :(得分:1)

创建函数模板时的区别在于它具有外部链接。所有翻译单位都可以访问相同的incAndShow。

从C ++标准工作草案N2798(2008-10-04)中解释: 14第4部分:非成员函数模板可以具有内部链接,其他函数模板总是具有外部链接。 14.8第2点:每个专业化都有自己的静态变量副本。

除非您在未命名的命名空间中声明它,否则您的函数模板应具有外部链接。因此,对于与函数模板一起使用的每个T,您应该获得一个用于吞吐程序的静态变量。换句话说,可以依赖程序中每个实例化只有一个静态变量(一个用于T == int,一个用于T == short等)。

顺便说一句,如果您在不同的翻译单元中以不同方式定义incAndShow,这可能会导致奇怪的情况。例如,如果您将其定义为在一个文件中递增而在另一个文件中递减(不通过将函数放入未命名的命名空间来指定内部链接),则两者将最终共享相同的函数,这将在编译时有效地随机选择(使用g ++,它取决于命令行上给定目标文件的顺序)。

答案 3 :(得分:0)

根据需要实例化模板,这意味着编译器(在这种情况下也是链接器?)将确保您不会得到同一模板的多个实例以及只有您需要的模板实例 - 在你的情况下,只有incAndShow<int>()被实例化而没有别的(否则编译器必须尝试为每个没有意义的类型实例化)。

所以我假设它用来确定实例化模板的类型的相同方法阻止它为同一类型实例化两次,例如只有incAndShow<int>()

的一个实例

这与非模板代码不同。

答案 4 :(得分:0)

是的,它是“正常的”,但无论你试图用这个“功能”实现什么,都可能有问题。尝试解释为什么要使用本地静态变量,可能我们可以想出一种更简洁的方法来实现它。

这是正常的原因是因为编译和链接模板函数的方式。每个翻译单元(在您的情况下为两个.cpp)都可以看到他们自己的incAndShow副本,当程序链接在一起时,两个incAndShow将合并为一个。如果你在头文件中声明你的常规函数​​内联,你会得到类似的效果。

答案 5 :(得分:-1)

以此示例显示绝对需要的行为:

#include <iostream>

template <class T> class Some
{
public:
   static int stat;
};

template<class T>
int Some<T>::stat = 10;

void main()
{
   Some<int>::stat = 5;
   std::cout << Some<int>::stat   << std::endl;
   std::cout << Some<char>::stat  << std::endl;
   std::cout << Some<float>::stat << std::endl;
   std::cout << Some<long>::stat  << std::endl;
}

你得到:5 10 10 10 10

以上显示静态变量的变化仅适用于“int”类型,因此在您的情况下您没有看到任何问题。

答案 6 :(得分:-3)

  • 模板只有在实例化(即使用)
  • 后才会实际转换为代码
  • 标题不能用于实现代码,只能用于声明