C++ Part I · Module 11 · Classes and Encapsulation
static Members
Chapter 11.2 stated that a class occupies no storage and its objects do, then admitted one exception without naming it. This is that exception. Some state genuinely belongs to a type rather than to any particular object of it — how many objects currently exist, a limit that applies to all of them, a shared lookup table that would be wasteful to duplicate per object — and for those the class is still the right place to declare it, even though no object contains it. The mechanism is short, and the chapter spends most of its length on the one part that reliably catches people out, which the frozen description of this chapter singles out: where such a member is actually defined. Writing it inside the class body looks exactly like declaring a data member and is not the same thing at all. It is a declaration only, the definition has to appear separately at namespace scope, and a program that uses the member without that definition anywhere compiles without a single complaint and then fails in the linker with a message that does not mention classes. That is the same failure shape Chapter 11.3 measured for member functions in headers, arriving from the opposite direction — there the symbol was defined too many times, here not at all. C++17 removed most of the chore for the common cases, and the chapter measures that too, against C++14, so you can see which behaviour depends on the standard you compile with.
Intermediate15 min readC++Part IFundamentalsClasses
Part I · Module 11 · Chapter 11.8 · static Members
1. State That Belongs to the Type
Chapter 11.2 §6 was categorical and left a gap:
"The class is a description. The objects are the things that exist."
Chapter 11.2 §8 then noted the exception: a static data member genuinely belongs to the type rather than to any object.
There is one of it, shared by every object of the class and existing even if no object exists. Three cases where that is what you want:
A shared limit or constant — the register width every field is checked against.
A count of instances, or any statistic about the type as a whole.
And a shared table that would be wasteful to duplicate in every object.
2. The Form
class Reg {
public:
static int count; // DECLARATION — not a definition
Reg() { ++count; }
int instanceId = 0;
};
int Reg::count = 0; // THE DEFINITION — exactly one, at namespace scopeTwo parts, and §4 is why that matters. Note the definition repeats the type, uses Reg::, and does not repeat static.
3. One Object, Shared — Measured
count = 3
a.count=3 b.count=3 &a.count=0x104520000 &b.count=0x104520000 same? YES - one object shared
sizeof(Reg) = 4 (static member is NOT in the object)Three facts in three lines.
It is shared. Three Reg objects were constructed and count is 3 — each constructor incremented the same variable.
The address is identical through two different objects. &a.count and &b.count are the same pointer. This is the measurement that makes "shared" precise rather than a claim: there is literally one variable.
And sizeof(Reg) is 4 — the size of the non-static instanceId alone. The static member is not in the object, exactly as Chapter 11.6 §4 measured for this.
Access works two ways, and the first is preferred:
Reg::count // through the type — says what it is
a.count // through an object — legal, and misleadingThe second form compiles and reads as though it were per-object. Prefer Reg::count.
4. The Declaration Is Not the Definition
This is what the chapter is really about.
#include <cstdio>
class Reg {
public:
static int count; // DECLARATION only
Reg() { ++count; }
};
int main() { Reg a, b, c; std::printf("count = %d\n", Reg::count); return 0; }Compiling it alone:
compile only (-c): 0 errorsLinking it:
Undefined symbols for architecture arm64:
ld: symbol(s) not found for architecture arm64
clang++: error: linker command failed with exit code 1Clean compile, failed link. The compiler was satisfied — it knew Reg::count existed and what type it had, which is all a declaration promises. The linker then looked for the actual variable and there was none.
Adding int Reg::count = 0; at namespace scope fixes it, and §3's output is the result.
Why it works this way: the class definition is in a header, included by many translation units (Chapter 2.6). If the declaration inside the class created the variable, every translation unit would create its own and the program would have many. So the class says this exists somewhere, and exactly one .cpp file says here it is.
Which makes the rule practical: the definition goes in one .cpp file, never in the header.
5. The Mirror Image of Chapter 11.3
Chapter 11.3 §4 measured the opposite failure, and the pair is worth holding together because both are linker errors from a header:
| Cause | Message | |
|---|---|---|
| Chapter 11.3 §4 | an out-of-line member function defined in a header, included twice | duplicate symbol 'Widget::outOfLine() const' |
| This chapter | a static data member never defined | Undefined symbols |
Too many definitions, or none. Both compile cleanly per file, and both are invisible until link. Chapter 2.7 is how to read such messages, and the general lesson is Chapter 2.6's: the compiler checks each translation unit alone, and only the linker sees the program.
6. C++17 Removed Most of the Chore
For the two most common cases the separate definition is no longer needed.
class Limits {
public:
static constexpr int maxWidth = 32; // C++17: implicitly inline
static inline int counter = 0; // C++17: inline variable
};Measured:
maxWidth = 32 counter = 0
address taken OK: 0x100474634No out-of-class definition, and taking the address worked — which matters, because taking the address is what used to force you to provide one.
The same file under -std=c++14:
warning: inline variables are a C++17 extension [-Wc++17-extensions]
clang++: error: linker command failed with exit code 1So this is genuinely a C++17 behaviour, and Chapter 1.3 §3 fixed this curriculum at C++17 — but it is worth knowing which rule you are relying on when reading older code, where you will see the separate definition everywhere.
The practical guidance:
For a compile-time constant, write static constexpr. It needs no definition, and constexpr states the stronger fact. Chapter 21.2 is constexpr properly.
For a mutable shared variable, static inline works and avoids the definition — though a mutable shared variable deserves the scepticism of §7.
For anything else — a non-constexpr object, a type with a constructor — the separate definition in one .cpp is still required.
7. static Member Functions
class Reg {
public:
static int count() { return count_; }
private:
static int count_;
};A static member function belongs to the type, so it has no object — and therefore no this. Measured:
class C { public: int n_ = 1; static int f() { return n_; } };error: invalid use of member 'n_' in static member functionAs Chapter 11.6 §6 noted, the message names the member rather than this, which is slightly indirect; the real reason is that there is no object for n_ to belong to.
So a static member function can use static members and nothing else. Call it through the type — Reg::count() — and use one when an operation is about the type rather than about an object: a factory, a conversion, a query over the shared state.
Two cautions on shared mutable state, because that is what a non-const static data member is.
It is global state with a class name on it. Everything that makes a global variable hard to reason about applies: any code can change it, there is one copy for the whole program, and tests cannot be isolated from each other. A per-object member is almost always better; reach for a static only when the state genuinely is not per-object.
And it has the static initialization order problem. Chapter 7.4 covered static objects and Chapter 10.5 §5 observed one constructed before main; the order in which static members across different translation units are initialized is not something you may rely on, and Module 24 is where that is treated properly. A static constexpr int has no such issue; a static member of class type does.
The pattern of a class holding a single shared instance of itself — a singleton — is built from the facilities in this chapter, and it inherits every problem above. It is named here so you recognise it, and it is not something this curriculum recommends.
8. Three Meanings of One Keyword
static now means three different things in this curriculum, and conflating them is a common and avoidable confusion:
| Where | Meaning | Chapter |
|---|---|---|
| a local variable in a function | static storage duration — one instance, lives for the program | 7.3 |
| a function or variable at namespace scope | internal linkage — not visible to other translation units | 7.5 |
| a member of a class | belongs to the type, not to any object | 11.8 |
The first and third both involve static storage duration; the second is about visibility and has nothing to do with lifetime. The keyword was reused, and the meaning is determined entirely by where it appears.
9. What to Actually Do
Use static constexpr for constants describing the type. No definition needed under C++17, no per-object storage.
Put the definition of any other static data member in exactly one .cpp file, never in the header.
Access through the type — Reg::count, not a.count.
Use a static member function for operations about the type, and expect no this.
Ask whether the state is genuinely type-level before making a mutable static member.
Be wary of static mutable state — it is global state, with the initialization-order problem and test coupling that implies.
Expect a clean compile and a link failure when you forget the definition; that is the signature.
And keep the three meanings of static straight — storage duration, linkage, class membership.
10. Mistakes Worth Making Once
Declaring a static data member and never defining it. Measured: 0 compile errors, then Undefined symbols.
Putting the definition in the header. Then you get Chapter 11.3 §4's failure instead — duplicate symbol.
Writing static again on the definition. It belongs only on the declaration.
Accessing it through an object — a.count reads as per-object and is not.
Expecting this in a static member function. error: invalid use of member 'n_' in static member function.
Assuming static inline works everywhere. Measured: a -Wc++17-extensions warning and a link failure under C++14.
Using a static counter for convenience. It couples the type's behaviour to the rest of the program and makes tests order-dependent.
Relying on initialization order across translation units. Module 24.
Confusing the three meanings of static.
11. Interview Relevance
"What is a static data member?" One variable shared by all objects of the type, not stored in any object. The measurement that lands is that &a.count and &b.count are the same address, and sizeof is unchanged.
"Where is it defined?" The class body only declares it; the definition goes once at namespace scope — and omitting it compiles cleanly and fails at link. Being able to say linker, and to name the message, is the distinguishing detail. In C++17, static constexpr and static inline need no separate definition.
"Can a static member function use this?" No — there is no object, so it can only touch static members.
"What are the downsides of static data members?" It is global state: shared mutability, the static initialization order problem across translation units, and test coupling.
"What are the different meanings of static in C++?" Storage duration for a local, internal linkage at namespace scope, and class membership for a member. Three meanings for one keyword is a favourite question.
12. What You Should Be Able to Say Now
- A
staticdata member belongs to the type, not to any object, and exists even with no objects. - Measured: three constructors gave
count = 3, and&a.count == &b.count— literally one variable. - Measured:
sizeof(Reg)was 4, the non-static member alone — the static member is not in the object. - The class body declares it; it must be defined once at namespace scope.
- Measured: omitting the definition gave 0 compile errors and
Undefined symbols for architecture arm64at link. - The definition repeats the type, uses
Reg::, and does not repeatstatic. - The mirror image is Chapter 11.3 §4's
duplicate symbol— too many definitions versus none, both invisible until link. - C++17:
static constexpris implicitly inline andstatic inlineworks — measured with the address taken successfully and no definition. - Measured: the same code under
-std=c++14gaveinline variables are a C++17 extensionand a link error. - A static member function has no
this—error: invalid use of member 'n_' in static member function. - Access through the type, not through an object.
- A mutable static member is global state — initialization order (Module 24) and test coupling.
statichas three meanings: storage duration (7.3), internal linkage (7.5), class membership (11.8).
13. Exercises
Exercise 1 — declare and define. Write a class Reg with a shared instance count and show both the declaration and the definition, saying which file each goes in.
Exercise 2 — predict the failure. A header declares static int Reg::count; inside the class and no .cpp defines it, while three files use it. Say what happens, at which stage, and what the message mentions.
Exercise 3 — the other failure. Now the definition is placed in the header. Say what happens and name the message.
Exercise 4 — C++17 or not. Which need a separate definition: static constexpr int w = 32;, static inline int n = 0;, static int n;, static const std::string s;? State the standard you are assuming.
Exercise 5 — no this. Explain why a static member function cannot read a non-static member, and why the error message names the member.
Exercise 6 — type-level or not? Apply §8's test to: a register width constant, a count of live transactions, a shared name-to-opcode table, a "current test name" string.
Exercise 7 — three meanings. Give the meaning of static in each: a local in a function, a function at namespace scope, a data member, and a member function.
14. Summary
- A
staticmember belongs to the type, not to any object, and exists with no objects. - Measured:
count = 3after three constructors,&a.count == &b.count, andsizeofunchanged at 4. - The class body only declares it — the definition goes once at namespace scope, in one
.cpp. - Measured: omitting it gives 0 compile errors and
Undefined symbolsat link. - The mirror image is Chapter 11.3's
duplicate symbol— none versus too many. - C++17's
static constexprandstatic inlineneed no definition — measured, including taking the address; under C++14 the same code warns and fails to link. - A static member function has no
thisand can touch only static members. - Access through the type, not an object.
- Mutable static state is global state — ordering and test coupling.
- Three meanings of
static: storage duration, internal linkage, class membership.
Eight chapters have supplied an argument and a mechanism: a type can promise something about its data, and here is every tool for expressing that. What has not happened is the part the module exists for. Every example so far has been chosen to isolate one feature — a counter to show that objects are independent, a window to show that setters cannot maintain a relational invariant, a register with two members because two is the smallest number that can relate. A real engineering description is not like that. It arrives as a paragraph of prose with units and ranges and special cases buried in it, several of the constraints are implied rather than stated, and the first decision is not which keyword to write but which of the facts in front of you are the invariant and which are merely true today. The last chapter of the module does that from end to end on a real register field, and the type it arrives at was compiled and exercised against every state it is supposed to refuse.
Standards & specifications
- Governing standard
- ISO/IEC 14882 (the C++ language standard)(opens ISO/IEC (JTC 1/SC 22/WG 21) in a new tab)
Defines the C++ language and its standard library — the type system and templates, the object model and object lifetime, initialisation and overload resolution, the memory model and the containers, algorithms and utilities the library provides. Like C's standard it is a FAMILY of editions (C++98/03, C++11, C++14, C++17, C++20, C++23) and the editions are NOT interchangeable: a feature this curriculum relies on may be unavailable, differently specified or merely proposed in another edition, so each lesson names the edition its claim depends on. It does not define any foreign-function interface to a hardware description language, nor what a compiler actually emits, nor platform behaviour on real silicon — those are the implementation's and the device's contracts. Working drafts published by WG21 are freely available and are what most engineers read day to day; a draft is not the published standard.
This page also covers RTL structure, verification approach and debugging technique. Those are engineering practice built on the standard, not requirements the standard itself imposes.
Where this fits
Part of the C++ Programming curriculum.
