43
class A {
  static int foo () {} // ok
  static int x; // <--- needed to be defined separately in .cpp file
};

I don't see a need of having A::x defined separately in a .cpp file (or same file for templates). Why can't be A::x declared and defined at the same time?

Has it been forbidden for historical reasons?

My main question is, will it affect any functionality if static data members were declared/defined at the same time (same as Java) ?

8
  • As a best practice, it is generally better to wrap your static variable in a static method (possibly as a local static) to avoid initialization order problems. Commented Apr 20, 2012 at 6:15
  • 2
    This rule is actually relaxed a bit in C++11. const static members usually don't have to be defined anymore. See: en.wikipedia.org/wiki/…
    – mirk
    Commented Apr 20, 2012 at 10:16
  • 4
    @afishwhoswimsaround: Specifying over generalized rules to all situations is not a good idea (best practices should be applied with context). Here you are trying to solve a problem that does not exist. The initialization order problem only affects object that have constructors and access other static storage duration objects. Since 'x' is int the first does not apply since 'x' is private the second does not apply. Thirdly this has nothing to do with the question. Commented Apr 20, 2012 at 11:07
  • 1
    Belongs on Stack Overflow? Commented Apr 24, 2013 at 11:13
  • 2
    C++17 allows inline initialization of static data members (even for non-integer types): inline static int x[] = {1, 2, 3};. See en.cppreference.com/w/cpp/language/static#Static_data_members Commented Feb 14, 2018 at 23:23

7 Answers 7

17

I think the limitation you have considered is not related to semantics (why should something change if the initialization were defined in the same file?) but rather to the C++ compilation model which, for reasons of backward compatibility, cannot be easily changed because it would either become too complex (supporting a new compilation model and the existing one at the same time) or would not allow to compile existing code (by introducing a new compilation model and dropping the existing one).

The C++ compilation model stems from that of C, in which you import declarations into a source file by including (header) files. In this way, the compiler sees exactly one big source file, containing all the included files, and all the files included from those files, recursively. This has IMO one big advantage, namely that it makes the compiler easier to implement. Of course, you can write anything in the included files, i.e. both declarations and definitions. It is only a good practice to put declarations in header files and definitions in .c or .cpp files.

On the other hand, it is possible to have a compilation model in which the compiler knows very well if it is importing the declaration of a global symbol that is defined in another module, or if it is compiling the definition of a global symbol provided by the current module. Only in the latter case the compiler must put this symbol (e.g. a variable) in the current object file.

For example, in GNU Pascal you can write a unit a in a file a.pas like this:

unit a;

interface

var MyStaticVariable: Integer;

implementation

begin
  MyStaticVariable := 0
end.

where the global variable is declared and initialized in the same source file.

Then you can have different units that import a and use the global variable MyStaticVariable, e.g. a unit b (b.pas):

unit b;

interface

uses a;

procedure PrintB;

implementation

procedure PrintB;
begin
  Inc(MyStaticVariable);
  WriteLn(MyStaticVariable)
end;
end.

and a unit c (c.pas):

unit c;

interface

uses a;

procedure PrintC;

implementation

procedure PrintC;
begin
  Inc(MyStaticVariable);
  WriteLn(MyStaticVariable)
end;
end.

Finally you can use units b and c in a main program m.pas:

program M;

uses b, c;

begin
  PrintB;
  PrintC;
  PrintB
end.

You can compile these files separately:

$ gpc -c a.pas
$ gpc -c b.pas
$ gpc -c c.pas
$ gpc -c m.pas

and then produce an executable with:

$ gpc -o m m.o a.o b.o c.o

and run it:

$ ./m
1
2
3

The trick here is that when the compiler sees a uses directive in a program module (e.g. uses a in b.pas), it does not include the corresponding .pas file, but looks for a .gpi file, i.e. for a pre-compiled interface file (see the documentation). These .gpi files are generated by the compiler together with the .o files when each module is compiled. So the global symbol MyStaticVariable is only defined once in the object file a.o.

Java works in a similar way: when then compiler imports a class A into class B, it looks at the class file for A and does not need the file A.java. So all definitions and initializations for class A can be put in one source file.

Going back to C++, the reason why in C++ you have to define static data members in a separate file is more related to the C++ compilation model than to limitations imposed by the linker or other tools used by the compiler. In C++, importing some symbols means to build their declaration as part of the current compilation unit. This is very important, among other things, because of the way in which templates are compiled. But this implies that you cannot / should not define any global symbols (functions, variables, methods, static data members) in an included file, otherwise these symbols could be multiply-defined in the compiled object files.

45

Since static members are shared between ALL instances of a class, they have to be defined in one and only one place. Really, they're global variables with some access restrictions.

If you try to define them in the header, they will be defined in every module that includes that header, and you'll get errors during linking as it finds all of the duplicate definitions.

Yes, this is at least partly a historical issue dating from cfront; a compiler could be written that would create a sort of hidden "static_members_of_everything.cpp" and link to that. However, it would break backwards compatibility, and there wouldn't be any real benefit to doing so.

10
  • 2
    My question is not the reason for current behavior, but rather the justification for such language grammar. In other words, suppose if static variables are declared/defined at the same place (like Java) then what can go wrong ?
    – iammilind
    Commented Apr 20, 2012 at 8:34
  • 8
    @iammilind I think you don't understand that the grammar is necessary because of the explaination of this answer. Now why? Because of the compilation model of C (and C++): c and cpp files are the real code file that are compiled separately like separate programs, then they are linked together to make a full executable. The headers are not really code for the compiler, they are only text to copy and paste inside c and cpp files. Now if something is defined several times, it cannot compile it, the same way it will not compile if you have several local variables with the same name.
    – Klaim
    Commented Apr 20, 2012 at 9:10
  • 1
    @Klaim, what about static members in template ? They are allowed in all the header files as they need to be visible. I am not disputing this answer, but it doesn't match my question also.
    – iammilind
    Commented Apr 20, 2012 at 9:16
  • @iammilind templates are not real code, they are code that generate code. Each instance of a template have one and only one static instance of each static declaration that is provided by the compiler. You still have to define the instance but as you define a template of an instance, it is not real code, as said above. Templates are, literally, templates of code for the compiler to generate code.
    – Klaim
    Commented Apr 20, 2012 at 9:46
  • 2
    @iammilind: Templates are typically instantiated in every object file, including their static variables. On Linux with ELF object files, the compiler marks the instantiations as weak symbols, which means that the linker combines multiple copies of the same instantiation. The same technology could be used to allow defining static variables in header files, so the reason it isn't done is probably a combination of historical reasons and compilation performance considerations. The entire compilation model will hopefully be fixed once the next C++ standard incorporates modules.
    – han
    Commented Nov 18, 2012 at 11:47
7

There is a great difference between C++ and Java.

Java operates on his own virtual machine that creates everything into its own run-time environment. If a definition happens to be seen more than once, will simply act on the same object the runtime environment ultimatelly knows.

In C++ there is no "ultimate knowledge owner": C++, C, Fortran Pascal etc. are all "translator" from a source code (CPP file) into an intermediate format (the OBJ file, or ".o" file, depending on the OS) where statements are translated into machine instruction and names becomes indirect addresses mediated by a symbol table.

A program is not made by the compiler, but by another program (the "linker"), that joins all OBJ-s together (no matter the language they come from) by re-pointing all the addresses that are towards symbols, towards their effective definition.

By the way the linker works, a definition (what creates the physical space for a variable) must be unique.

Note that C++ does not by itself link, and that the linker is not issued by C++ specs: the linker exist because of the way the OS modules are built up (usually in C and ASM). C++ has to use it the way it is.

Now: a header file is something to be "pasted into" several CPP files. Every CPP file is translated independently of every other one. A compiler translating different CPP files, all receiving-in a same definition will place the "creation code" for the defined object in all the resulting OBJs.

The compiler doesn't know (and will never know) if all those OBJs will ever be used together to form a single program or separately to form different independent programs.

The linker doesn't know how and why definitions exist and where they come from (it even doesn't know about C++: every "static language" can produce definitions and references to be linked). It just knows there are references to a given "symbol" that is "defined" at a given resulting address.

If there are multiple definitions (don't confuse definitions with references) for a given symbol, the linker has no knowledge (being language agnostic) about what to do with them.

It is like merging a number of city to form a big town: if you're found having two "Time square" and a number of people coming from outside asking to go to "Time square", you cannot decide on a pure technical basis (without any knowledge about the politics that assigned those names and will be in charge to manage them) in which exact place to send them.

2
  • 3
    The difference between Java and C++ with respect to global symbols is not connected with Java having a virtual machine, but rather with the C++ compilation model. In this respect, I would not put Pascal and C++ in the same category. Rather I would group C and C++ together as "languages in which the imported declarations are included and compiled together with the main source file" as opposed to Java and Pascal (and maybe OCaml, Scala, Ada, etc) as "languages in which the imported declarations are looked up by the compiler in pre-compiled files containing information about exported symbols".
    – Giorgio
    Commented Nov 18, 2012 at 11:41
  • 1
    @Giorgio: the reference to Java may not be welcome, but I think that Emilio's answer is mostly right by getting to the gist of the issue, namely the object file/linker phase after separate compilation.
    – ixache
    Commented Sep 20, 2013 at 10:18
6

The probable reason for this is that this keeps the C++ language implementable in environments where the object file and linkage model does not support the merging of multiple definitions from multiple object files.

A class declaration (called a declaration for good reasons) gets pulled into multiple translation units. If the declaration contained definitions for static variables, then you would end up with multiple definitions in multiple translation units (And remember, these names have external linkage.)

That situation is possible, but requires the linker to handle multiple definitions without complaining.

(And note that this conflicts with the One Definition Rule, unless it can be done according to the kind of a symbol or what kind of section it is placed in.)

5

Its required because otherwise the compiler doesn't know where to put the variable. Each cpp file is individually compiled and does not know about the other. The linker resolves variables, functions, etc. I personally don't see what the difference is between the vtable and static members (we don't have to choose what file the vtable are defined in).

I mostly assume it is easier for compiler writers to implement it that way. Static vars outside of class/struct exist and perhaps either for consistency reasons or because it would be 'easier to implement' for compiler writers they defined that restriction in the standards.

2

I think I found the reason. Defining static variable in separate space allows to initialize it to any value. If not initialized then it will defaulted to 0.

Before C++11 the in class initialization was not allowed in C++. So one cannot write like:

struct X
{
  static int i = 4;
};

Thus now to initialize the variable one must write it outside the class as:

struct X
{
  static int i;
};
int X::i = 4;

As discussed in other answers too, int X::i is now a global and declaring global in many files causes multiple symbol link error.

Thus one has to declare a class static variable inside a separate translation unit. However, still it can be argued that following way should instruct compiler not to create multiple symbols

static int X::i = 4;
^^^^^^
0

A::x is just a global variable but namespace'd to A, and with access restrictions.

Someone still has to declare it, like any other global variable, and that may even be done in a project that is statically linked to the project containing the rest of the A code.

I would call these all bad design, but there are a few features you can exploit this way:

  1. the constructor call order... Not important for an int, but for a more complex member that maybe accesses other static or global variables, it can be critical.

  2. the static initializer - you can let a client decide what A::x should be initialized to.

  3. in c++ and c, because you have full access to memory through pointers, the physical location of variables is significant. There are very naughty things you can exploit based on where a variable is located in a link object.

I doubt these are "why" this situation has arisen. It's probably just an evolution of C turning into C++, and a backwards compatibility issue that stops you from changing the language now.

1
  • 2
    this doesn't seem to offer anything substantial over points made and explained in prior 6 answers
    – gnat
    Commented Feb 23, 2017 at 8:17

Not the answer you're looking for? Browse other questions tagged or ask your own question.