【问题标题】:Static variable inside template function模板函数内的静态变量
【发布时间】:2010-11-02 22:06:48
【问题描述】:

在 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 错误。同样,两个不同的 .cpp 调用具有相同模板(例如 incAndShow&lt;int&gt;())的函数,将共享 myStaticVar。这是正常的吗?我在问这个问题,因为我确实依赖于这个“特性”(共享静态变量),并且我想确保这样做的不仅仅是我的实现。

【问题讨论】:

  • +1 好问题。现在我正在实现一些“依赖此功能”

标签: c++ templates static


【解决方案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”类型,因此在您的情况下您看不到任何问题。

【讨论】:

  • -1:这个例子演示了静态类成员;问题是询问函数内部的静态变量。
【解决方案2】:

您可以信赖这一点。 ODR(一个定义规则)在标准中的3.2/5 表示,其中D 代表非静态函数模板(我的草书字体)

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

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

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

编辑

我认为仅此一项不足以保证您在不同实例中的静态变量都是相同的。以上仅保证模板的多个定义有效。它没有说明由此产生的专业化。

这是 linkage 发挥作用的地方。如果函数模板特化(它是一个函数)的名称具有外部链接 (3.5/4),那么引用此类特化的名称是指到相同的功能。对于声明为静态的模板,从它实例化的函数具有内部链接,因为

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

具有命名空间范围 (3.3.6) 的名称如果是 [...] 明确声明为静态 -- 3.5/3 的对象、引用、函数或函数模板的名称,则具有内部链接

如果函数模板没有用静态声明,那么它有外部链接(顺便说一下,这也是我们必须遵循 ODR 的原因。否则,D 不会被多重定义完全没有!)。这可以来自14/4(连同3.5/3

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

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

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

当它具有内部链接时,3.5/3 解释了显式特化提供的功能,14/4 解释了生成的特化(模板实例化)。由于您的模板名称具有外部链接,因此您的所有专业化都具有外部链接:如果您使用来自不同翻译单元的名称(incAndShow&lt;T&gt;),它们将引用相同的功能,这意味着您的静态对象在每种情况下都是相同的.

【讨论】:

    【解决方案3】:

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

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

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

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

    【讨论】:

      【解决方案4】:

      是的,这是“正常的”,但无论您尝试使用此“功能”实现什么,都可能有问题。试着解释一下为什么要使用局部静态变量,也许我们可以想出一个更简洁的方法来做到这一点。

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

      【讨论】:

        【解决方案5】:

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

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

        这与非模板代码不同。

        【讨论】:

          【解决方案6】:
          • 模板只有在实例化(即使用)后才会真正转化为代码
          • 标头不能用于实现代码,而只能用于声明

          【讨论】:

          • 伙计们,如果您不屑于对我投反对票,我会很高兴就问题所在发表评论 - 显然最初的问题很模糊......
          • “标头不能用于实现代码,而只能用于声明” 不适用于模板。您需要在标题中定义(“实现”)模板。 export 是解决这个问题的一种方法,但事实证明这太难了,而且通常没有实施。
          【解决方案7】:

          所以我明白你的问题。您在问每个版本的模板化函数是否有自己的 myStaticVar 实例是否正常。 (例如:incAndShow&lt;int&gt; vs. intAndShow&lt;float&gt; 答案是肯定的。

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

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2017-05-26
            • 1970-01-01
            • 2010-12-05
            • 1970-01-01
            • 2013-11-04
            • 1970-01-01
            • 2016-08-30
            • 2016-04-27
            相关资源
            最近更新 更多