9

我最近在一个头文件中看到了这段代码,很惊讶它的工作原理:

namespace NS {
  static int uid = 0;
  class X {
   public:
    static int getUID() { return uid++; }
  };
}

如果从几个不同的 C++ 源文件调用静态方法NS::X::getUID(),我惊讶地发现它正确生成了唯一 ID(跨翻译单元唯一)。我认为命名空间范围内的静态变量与翻译单元有内部链接。这里发生了什么?类 X 中的内联静态方法是否有它自己的翻译单元,这就是它生成唯一 ID 的原因?还是由于我的编译器中的怪癖而对我有用?

上面的代码是否依赖于“安全”的定义良好的行为?如果是这样,这是一种在内联或模板类中生成唯一 ID 的出奇简洁的方法,即使它看起来有点笨拙。还是像这样为静态唯一 ID 函数生成一个新的 C++ 源文件,并将静态 ID 移动到类中更好?

更新:

对于一个测试用例,我在不同的文件(file1.cpp、file2.cpp 等)中编写了几个这样的函数:

#include "static_def.h" // Name of the above header file.
void func1() {
  int uid1 = NS::X::getUID();
  int uid2 = NS::X::getUID();
  std::cout << "File1, UID1: " << uid1 << ", UID2: " << uid2 << std::endl;
}

令人惊讶的输出(在从 main 调用这些之后)是:

File1, UID1: 0, UID2: 1
File2, UID1: 2, UID2: 3
4

2 回答 2

14

仅当您仅将此标头包含在单个源文件中时,您的代码才是正确的。

因为uid被声明为static,所以它具有内部链接。uid每个包含此标头的源文件都有一个变量实例。如果你在三个源文件中包含这个头文件,就会有三个uid变量。

getUid函数是隐式inline的,因为它是在类定义中定义的。static它是成员函数的事实是无关紧要的。函数的规则inlineinline函数必须在使用它的每个源文件中定义,并且所有定义必须相同。

您的getUid函数定义违反了这条规则:是的,它定义在每个包含头文件的源文件中(因为它是在头文件中定义的),但是每个定义都是不同的,因为在每个定义中uid引用的是不同的uid变量。

因此,您的程序违反了单一定义规则并表现出未定义的行为。您观察到的特定行为可能是因为编译器选择了内联函数的一个定义并丢弃了其余的定义,因此您认为您正在使用的“全局变量”恰好是变量之一uid——无论哪个被引用getUid编译器保留的副本。虽然这是这种未定义行为形式的典型表现,但该行为仍然是未定义的。

您可以使uid变量函数局部化以确保只有一个实例,并避免违反单一定义规则:

namespace NS {
  class X {
  public:
    static int getUID() {
      static int uid = 0;
      return uid++;
    }
  };
}

在这种情况下,可以保证uid程序中只有一个 的实例。

于 2013-03-28T15:36:27.577 回答
2

我相信您的编译器通过不内联getUID()方法来欺骗您。隐式内联的事实getUID()并不意味着它实际上会被内联——这仅仅是对编译器的建议。

除此之外,一切正常:UID 具有内部链接,getUID()也可以返回非唯一 ID。

于 2013-03-28T15:54:27.720 回答