Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does a static namespace variable accessed from inside an inline class method work?

Tags:

c++

static

I saw this code recently in a header file, and was surprised that it worked:

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

If the static method NS::X::getUID() is called from several different C++ source files, I was surprised to find that it correctly generated a unique ID (unique across translation units). I thought that a static variable in a namespace scope had internal linkage to the translation unit. What's going on here? Does the inline static method in class X have it's own translation unit, and that's why it generates a unique ID? Or is it working for me due to a quirk in my compiler?

Does the above code rely on "safe" well-defined behavior? If so, this is a surprisingly concise method of generating a Unique ID in an inline or template class, even if it looks a bit kludgy. Or is it better to generate a new C++ source file for a static unique ID function like this, and move the static ID inside the class?

Update:

For a test case, I wrote several functions like this in different files (file1.cpp, file2.cpp, etc.):

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

The suprising output (after calling these from main) was:

File1, UID1: 0, UID2: 1
File2, UID1: 2, UID2: 3
like image 655
Ogre Psalm33 Avatar asked Mar 28 '13 15:03

Ogre Psalm33


People also ask

Can inline functions have static variables?

Static local variables are not allowed to be defined within the body of an inline function. C++ functions implemented inside of a class declaration are automatically defined inline.

Can static variables be accessed outside the class?

From outside the class, "static variables should be accessed by calling with class name." From the inside, the class qualification is inferred by the compiler.

How do static variables work in C++?

A static variable is a variable that is declared using the keyword static. The space for the static variable is allocated only one time and this is used for the entirety of the program. Once this variable is declared, it exists till the program executes.

What does it mean for a variable inside of a function to be marked static?

In computer programming, a static variable is a variable that has been allocated "statically", meaning that its lifetime (or "extent") is the entire run of the program.


1 Answers

Your code is only correct if you only ever include this header in a single source file.

Because uid is declared as static, it has internal linkage. There is one instance of the uid variable per source file in which this header is included. If you include this header in three source files, there will be three uid variables.

The getUid function is implicitly inline because it is defined inside of the class definition. The fact that it is a static member function is irrelevant. The rule for inline functions is that an inline function must be defined in every source file in which it is used, and that all of the definitions must be identical.

Your getUid function definition violates this rule: yes, it is defined in every source file that includes the header (because it is defined in the header), but each definition is different, because in each definition the uid referred to is a different uid variable.

Therefore, your program violates the One Definition Rule and exhibits undefined behavior. The particular behavior you observe is likely because the compiler picks one definition of the inline function and just discards the rest, so the "global variable" that you think you are using just happens to be one of the uid variables--whichever one was referenced by the copy of getUid that the compiler kept. While this is a typical manifestation of this form of undefined behavior, the behavior is nonetheless undefined.

You can make the uid variable function-local to ensure that there is exactly one instance, and to avoid violating the One Definition Rule:

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

It is guaranteed in this case that there will be exactly one instance of uid in the program.

like image 66
James McNellis Avatar answered Nov 05 '22 04:11

James McNellis