Why is declaration and definition defined this way in Effective C++?
Asked Answered
K

3

22

In Effective C++ (3rd Ed.), Item 2 (Prefer const, enum and inline to #define), the code segment for class-specific constants read:

class GamePlayer {
private:
    static const int NumTurns = 5;    // constant declaration
    int scores[NumTurns];             // use of constant
    ...
};

The book then says (in my own words) that static const int NumTurns = 5; is not a definition, which is normally required by C++ for class members unless it is a static integral constant whose address is never used. If the above is not true for the constant or if the compiler insists on a definition for any reason, the definition should be provided in the implementation file as follows:

const int GamePlayer::NumTurns;    // definition of NumTurns; see
                                   // below for why no value is given

According to the book (also in my own words), no value is given in the definition because it's already given in the declaration.

This is confusing what I thought I already know about the definitions of declaration and definition (and I double-checked on Google before asking this question):

  • Why is static const int NumTurns = 5 not a definition? Is NumTurns not initialized to a value of 5 here, and is it not that when a declaration and a definition occurs together, it is called an initialization?
  • Why do static integral constants not require a definition?
  • Why is the second code snippet considered to be a definition when no value is defined, yet the declaration inside the class which contains the value is still not one (essentially going back to my first question)?
  • Isn't initialization a definition? Why is the "only one definition" rule not violated here?

It's possible I'm just confusing myself here at this point, so can someone kindly re-educate me from scratch: Why are those two lines of code declarations and definitions and not the other, and are there any instances of initialization there? Is initialization also a definition?

Credit: Code snippets are directly quoted from the book.

Edit: With additional reference to What is the difference between a definition and a declaration?

  • A declaration introduces the identifier and type
  • A definition instantiates and implements

So, yeah...that doesn't seem to be what's going on here.

Edit 2: I consider it is possible that compilers may optimize a static integral constant by not storing it in-memory and just substituting it inline in the code. But if NumTurns address is used, why doesn't the declaration change into a declaration+definition automatically since the instantiation is already there?

Edit 3: (This edit has less to do with the original question, but is still outstanding from it. I place it here so that I don't need to copy-paste into the comments for each answer below. Please answer me for this in the comments. Thanks!)

Thanks for the answers. My head is much clearer now, but the last question from Edit 2 still remains: If the compiler detects conditions in the program where a definition is needed (eg. &NumTurns is used in the program), why doesn't it just automatically reinterpret static const int NumTurns = 5; as a declaration & definition instead of declaration-only? It has all the syntax a definition anywhere else in a program would have.

I come from a Java background in school, and have not needed to make separate definitions like these for static members in the manner above. I know C++ is different, but I want to know why the above is like this. A static integral member being substituted inline if the address is never used sounds more like an optimization to me rather than a fundamental feature, so why is it I need to work around it (providing a separate statement as the definition when the conditions aren't met even though the original statement's syntax is sufficient) rather than the other way round (compiler treating the original statement as a definition when it is needed to have one since syntax is sufficient)?

Kati answered 6/1, 2016 at 9:42 Comment(12)
@juanchopanza Edited & clarified.Kati
It boils down to whether the translation unit including the class definition needs to itself allocate memory for the constant, or whether it trusts some other translation unit will create an object containing the constant, and the current translation unit can treat it as extern. It's only when you have the definition (sans value) that the current translation unit allocates space in the object being created. That way, all translation units access the same variable once linked.Foreignism
You're confused by the default value given by the declaration, not the definition. You could give the value with the definition as well, but you could not omit the definition (which is what actually brings NumTurns into existence). The = 5 in the declaration only sets a default, it does not set a value. The definition (const int GamePlayer::NumTurns;) uses that default; it could provide a non-default initialization as well.Glossematics
@Glossematics It does set a value in the sense that the constant can be used in-class by inline substitution for that value. But yes, this value is not available externally (or for address-of) until defined outwith the class.Caro
@underscore_d: Geeez... missed that one, thanks for the heads-up.Glossematics
Effective C++ itself explains that (just as @TonyD says) only definitions actually cause the compiler to allocate memory for something, yet you don't even mention memory until your second edit. I agree that this is confusing, but the problem here is that you're attempting to make Meyers' examples fit your preconceived (and then quickly googled for affirmation) notion of the distinction between "definition" and "declaration", rather than trying to understand and then applying his definition given in the book you're reading.Verbal
"why doesn't it just automatically reinterpret static const int NumTurns = 5; as a declaration & definition instead of declaration-only?" - not sure if you're still unclear on this; the reason is that it might end up doing that in multiple translation units, therefore reserving distinct space in multiple objects/libraries, whereas the desired behaviour it to have one copy of the constant used by all the objects contributing to a program. I've no idea what Java does - perhaps it wastes memory on extra copies, perhaps they're consolidated by the VM or byte compiler....Foreignism
@TonyD but being static means there'll only ever be at most one instance at any moment. I remember reading that, at least in Java, a static member is compiled as part of the class definition, stored as a value in memory, with its own address, inside the memory where the class definition is located. When it is used, the contents of this memory address is accessed. It still will only be one definition in the whole program, and so will only be located in one place.Kati
@thegreatjedi: in C++, you have to orchestrate that by defining the static variable in a single translation unit / object-file (loosely speaking, a single .c++/.cpp/.cc/.cxx or whatever you like to call your "implementation" files)... that's the lesson to be learnt from this question & the answers.Foreignism
@TonyD I know. What I mean is - the syntax for the static integral const with a default value is identical to definitions in other parts of a program, so why isn't it used as such when needed? If we compare the above code snippets to normal syntax, the above declaration has the syntax of a definition, and the above definition - necessary only when one is needed - has the syntax of a declaration. Doesn't that look weird? Why isn't the declaration simultaneously treated as a definition when needed so you don't need that extra line in the first place?Kati
"so why isn't it used as such when needed?" - because each translation unit that felt such a need would do so independently. Instead, it must be coordinated across the program by the programmer. Perhaps it will help to think of it as a tolerance allowed from the default coding practice of declaring non-const variables in the header without assignment, and defining them in one implementation file with actual values. cont...Foreignism
...For const variables, it helps to let the compiler use the variable much as a macro - substituting the constant value where used as a template parameter, to size an array, or other uses at compile time that have no need of the address. So, think of this as the non-const model with benefits that arise from letting the value be specified at the declaration. A necessary tolerance to make static const variables a viable, equally efficient but type safe and namespace/scope-respecting alternative to preprocessor defines.Foreignism
S
13

Disclaimer: I am not a standard guru.

I suggest you read the following two :

http://www.stroustrup.com/bs_faq2.html#in-class
and:
What is the difference between a definition and a declaration?

Why is static const int NumTurns = 5 not a definition? Is NumTurns not initialized to a value of 5 here, and is it not that when a declaration and a definition occurs together, it is called an initialization?

In high level, definition (as opposed to declaration) instantiates or implements the entity (variable, class, function).
In case of variables, definition makes the variable be allocated on the program memory.
In case of functions - definition gives instructions that can be compiled to assembly instructions.*

The line of code

static const int NumTurns = 5;

does not make NumTurns be allocated in the program memory by default, so it's only declaration. In order to make (the only) instance of NumTurns in the program memory you will have to provide the needed definition:

const int MyClass::NumTurns;  

Bjarne quote:

You can take the address of a static member if (and only if) it has an out-of-class definition:

class AE {
    // ...
public:
    static const int c6 = 7;
    static const int c7 = 31;
};

const int AE::c7;   // definition

int f()
{
    const int* p1 = &AE::c6;    // error: c6 not an lvalue
    const int* p2 = &AE::c7;    // ok
    // ...
}

Why do static integral constants not require a definition?

They do if you want to take their addresses.

Isn't initialization a definition?

No. initialization is the act of setting initial values in some entity (primitive,object).

void someFunc (){
  int x; //x is declared, but not initialized
  int y = 0; //y is declared+ initialized.
}

Why is the "only one definition" rule not violated here?

Why would it ? You still have one NumTurns in the entire code. The symbol MyClas::NumTurns appears only once. The definition only makes the variable appear on the program memory. ODR rule specify that any symbol may be declared only once.

EDIT: The question basically boils down to "Why can't the compiler decide on its own?" I am not a member of the comitee, so I can't give fully legit answer.
My smart guess is that letting the compiler decide things is not the C++ philosophy. when you let the compiler decide , e.g. where to declare a static integral const, I, as a developer, may raise the following issues:

1) what happens If my code is a headers-only library? where should my compiler declare the variable? in the first cpp file it encounters? in the file which containes main ?

2) if the compiler decides where to declare the variable , does I have any any guarantee that this variable will be declared alongside with other static variables (which I have manually declared), thus keeping the cache locality tight?

there are more and more questions raised as soon as we approve the mindset of "let the compielr go wild" I think this is a fundamental difference between Java and C++.

Java: let the JVM do its hueristics.
C++: let the developer do his profiling.

eventually, in C++ , the compiler checks that everything makes sence and turn the code into binary, not do the job instead of the developer.

*or bytecode, if you use managed C++ (boo...)

Soulier answered 6/1, 2016 at 10:5 Comment(5)
"managed C++"? Wow, I had no idea such a thing existed. Google's summary from Wikipedia defines it verbatim as "a now deprecated Microsoft set of deviations", which is simultaneously predictable, relieving, and hilarious.Caro
@Caro you can still use C++ with microsoft CLR, it's not deprecated. I personaly think that C++ can be faster if it will compile first wit static compiler and then use JIT compilation for hot-spot optimizations (de-virtualizations , dynamic cache managemant, etc.) then you can have the best from the two approachesSoulier
@Caro It's not called "Managed C++" anymore, that is deprecated. The current product is C++/CLI, which you get with Visual Studio.Elaterid
@DavidHaim Hi, thanks for the answer. I still have one outstanding issue, please see edit 3Kati
Someone needs to ask Bjarne to fix his FAQ. AE::c6 is an lvalue just like AE::c7.Brierwood
B
7

One of the consequences of the one-definition rule is that a static member of a class can only have one definition. However, if multiple compilation units define it, there would be one definition for every compilation unit within a program. The net effect is that the declaration cannot be a definition without breaking the one-definition rule. In practice, linkers are not typically smart enough to resolve such multiple definitions.

The reason static integral constants do not require a definition is that it is not necessary. If the value is initialised within the class definition, the compiler can just substitute the initialised value whenever it is used. Practically, this means there is no need for that value to actually occupy a memory location in the program (as long as no code computes the address of that constant, in which case a definition would be needed).

Declaration, definition, initialisation are actually separate (albeit related) concepts. A declaration tells the compiler something exists. A definition is a type of declaration that causes that something to exist (so code with visibility of other declarations can refer to it) - for example, allocates memory for it. Initialisation is the act of giving a value. This distinction actually occurs in other parts of the language. For example;

#include <iostream>
int main()
{
     int x;   //  declaration and definition of x

     std::cout << x << '\n';    // undefined behaviour as x is uninitialised

     x = 42;   // since x is not yet initialised, this assignment has an effect of initialising it

     std::cout << x << '\n';    // OK as x is now initialised
}

In practice, an initialisation can be part of a declaration, but is not required to be.

Edited to respond to "Edit 3" in the original question:

C++ has a separate compilation model. Java's model relies on capability (smarter linker, run time linking) that C++'s model does not. In C++, if one compilation unit sees a declaration but no definition, the compiler simply assumes the definition is in another compilation unit. Typically (with a lot of build chains) the linker later detects if a necessary definition does not exist, so the linking stage fails. Conversely, if every compilation unit that needed a definition to exist actually created one, the compiler would break the "one definition rule", and a typical dumb linker - which among other things is not smart enough to collapse something defined repeatedly into a single definition - would complain about a multiply-defined symbol.

Bismuthinite answered 6/1, 2016 at 10:8 Comment(3)
the initialisation of const statics within class definitions came into existence in recent standards. Well, I don't think so.Premedical
@Bismuthinite Hi, thanks for the answer. I still have one outstanding issue, please see edit 3Kati
Answer edited to respond to your "Edit 3". Yes, C++ is different from Java.Bismuthinite
H
5

Why is static const NumTurns = 5 not a definition? Is NumTurns not initialized to a value of 5 here, and is it not that when a declaration and a definition occurs together, it is called an initialization?

That's the wrong question. A declaration is where the compiler is notified that a type/variable/function/whatever exists, and what it is. A definition is where the compiler is instructed to actually allocate storage to keep said entity.

Since the member is "defined" inside the class' declaration (i.e. - no instance of the class is created at that point), then this is a declaration.

The equal sign you rely on in order to call this a definition is merely a default value for the struct member, not an initialization.

Why do static integral constants not require a definition?

You kinda answered that one yourself. It is because the compiler can avoid allocating any storage to them, and simply drop them into the code where used.

Why is the second code snippet considered to be a definition when no value is defined, yet the declaration inside the class which contains the value is still not one (essentially going back to my first question)?

As I said before, that's because the second code allocates storage for the variable.

Isn't initialization a definition? Why is the "only one definition" rule not violated here?

Because initialization is not definition.

If the compiler detects conditions in the program where a definition is needed (eg. &NumTurns is used in the program), why doesn't it just automatically reinterpret static const int NumTurns = 5; as a declaration & definition instead of declaration-only?

Because a definition allocates the storage. More specifically, because if the compiler did this, then there would be multiple storage allocations in different compilation units. This is bad, because, during linking, the linker needs to have only one. The linker sees several definitions of the same variable with the same scope, and does not know it has the same value. All it does know is that it cannot merge them all into a single location.

In order to avoid that problem, it makes you do the definition manually. You define it inside a cpp file (i.e. - not in the header), thus resolving the allocation to one specific file which the linker can stomach.

Haste answered 6/1, 2016 at 10:8 Comment(2)
Hi, thanks for the answer. I still have one outstanding issue, please see edit 3Kati
@thegreatjedi, if you got your answer, please accept the one you found most useful.Haste

© 2022 - 2024 — McMap. All rights reserved.