C++ · lesson 24 of 24
Beginner
6Intermediate
11- 5 Operators and Operator Overloading in C++
- 8 Classes and Object-Oriented Programming in C++
- 9 Inheritance and Polymorphism in C++
- 11 STL Containers in C++
- 12 STL Algorithms in C++
- 13 Smart Pointers in C++
- 15 Lambdas and Functional Programming in C++
- 18 Error Handling in C++
- 19 File I/O in C++
- 23 C++ Interview Preparation: Top 35 Questions
- 24 Static Members in C++
Advanced
7Static Members in C++
Learn static data members and static member functions in C++ — shared per-class state, instance counters, and factory functions that run without an object.
By SkillByExample · Published
Static Members in C++
A static data member belongs to the class itself, not to any one object — every instance
shares the same copy. A static member function goes further: it has no this pointer at all,
so it can be called through the class name without an object existing. Both are the mechanism
for state and behavior that’s per-class rather than per-instance.
Static Data Members — Shared State
Declaring a member static inside the class only reserves its name; it still needs a
definition elsewhere that gives it storage and an initial value.
#include <iostream>
class Widget {
static int liveCount_; // declaration only — shared by every Widget
public:
Widget() { ++liveCount_; }
~Widget() { --liveCount_; }
static int liveCount() { return liveCount_; } // no *this — reads shared state only
};
// One definition, outside the class, gives liveCount_ storage and its initial value
int Widget::liveCount_ = 0;
int main() {
std::cout << "before: " << Widget::liveCount() << "\n";
Widget a, b;
std::cout << "after a, b: " << Widget::liveCount() << "\n";
{
Widget c;
std::cout << "after c: " << Widget::liveCount() << "\n";
} // c destroyed here
std::cout << "after c's scope ends: " << Widget::liveCount() << "\n";
}
$ g++ -std=c++17 -Wall -Wextra -o counter counter.cpp
$ ./counter
before: 0
after a, b: 2
after c: 3
after c's scope ends: 2
liveCount_ reaches 0 before any Widget exists, climbs as constructors run, and drops as
destructors run when c goes out of scope — one counter, tracking every object of the class at
once, with no object needed to read it (Widget::liveCount(), not a.liveCount()).
Static Member Functions as Factories
Because a static member function has no this, it can’t touch liveCount_’s non-static
cousins on some particular object — but it can call a private constructor, which makes it
useful for enforcing invariants before an object is ever built.
#include <iostream>
#include <string>
class Account {
static constexpr double MIN_BALANCE = 10.0; // shared constant — no storage per object
std::string owner_;
double balance_;
// Private constructor: the only way to build one is through the static factory below
Account(std::string owner, double balance) : owner_(std::move(owner)), balance_(balance) {}
public:
// Static member function: called via Account::open(...), not on an instance
static Account open(const std::string& owner, double deposit) {
if (deposit < MIN_BALANCE)
deposit = MIN_BALANCE; // enforce the invariant before construction
return Account(owner, deposit);
}
void print() const {
std::cout << owner_ << ": $" << balance_ << "\n";
}
};
int main() {
Account a = Account::open("Priya", 50.0);
Account b = Account::open("Sam", 2.0); // below minimum — factory bumps it up
a.print();
b.print();
}
$ g++ -std=c++17 -Wall -Wextra -o factory factory.cpp
$ ./factory
Priya: $50
Sam: $10
Account’s constructor is private, so Account("Sam", 2.0) won’t compile from main — the
only entry point is Account::open, which can call the private constructor because it’s a
member of the same class. MIN_BALANCE needs no per-object storage either: static constexpr
makes it a single compile-time constant shared by the class, not copied into every Account.
Practice
-
Add a
static int totalDeposited_toAccountthat accumulates every deposit passed toopen, plus astatic double totalDeposited()accessor. Print the total after creating a few accounts.Solution
static double totalDeposited_; static double totalDeposited() { return totalDeposited_; } static Account open(const std::string& owner, double deposit) { if (deposit < MIN_BALANCE) deposit = MIN_BALANCE; totalDeposited_ += deposit; return Account(owner, deposit); } // outside the class: double Account::totalDeposited_ = 0.0; -
Remove the out-of-class definition
int Widget::liveCount_ = 0;and try to compile. Explain the linker error you get.Solution
The linker reports an undefined reference to
Widget::liveCount_. The in-class line only declares the member; without a definition it has no storage, so nothing exists for++liveCount_to increment. -
Change
Widget::liveCount()to a non-static member function (dropstatic, keep the body). Try callingWidget::liveCount()the same way as before. What error do you get, and why?Solution
A compile error: a non-static member function requires an object (
a.liveCount()), because it implicitly takesthis.Widget::liveCount()with no object only works for static members, which don’t need one. -
Add a static member function
Widget::reset()that setsliveCount_back to0, and call it after the destructors have already run inmain. Does it compile even though noWidgetexists at that point?Solution
Yes —
static int reset() { liveCount_ = 0; return liveCount_; }compiles and runs fine with zero live objects, because static members belong to the class, not to any instance. -
Make
MIN_BALANCEa plain (non-constexpr)static const doubleinstead, and remove the in-class initializer, addingdouble Account::MIN_BALANCE = 10.0;outside the class instead. Confirm it still compiles and behaves the same.Solution
static const double MIN_BALANCE; // declaration only, no initializer in-class // outside the class: const double Account::MIN_BALANCE = 10.0;This is the pre-C++17 pattern
static constexprwas designed to avoid — it still works, but needs the same out-of-class definition every other static data member needs.