Consider a class with member variables. Each time an instance is made, each object gets a duplicate of the member variables.
A member variable can be assigned static. This means all class objects share (reference) the same member. This allows the member variable to be accessed from multiple objects.
One application is to keep track of the amount of objects created. In the example below, get object is assigned a unique ID
#include <iostream>
class Ticket {
private:
static inline int s_nextId { 1 };
int m_id {};
public:
Ticket()
: m_id { s_nextId++ }
{
}
int id() const
{
return m_id;
}
};
int main()
{
Ticket first {};
Ticket second {};
Ticket third {};
std::cout << first.id() << '\n';
std::cout << second.id() << '\n';
std::cout << third.id() << '\n';
}
Terminal output:
1
2
3
All class instances reference the same static member variable. No duplicates are made.
Hooray for sharing!
Static members are not tied to one object
A static data member can be accessed without creating an object using ::
#include <iostream>
class AppSettings {
public:
static inline int maxRetries { 3 };
};
int main(){
std::cout << AppSettings::maxRetries << '\n';
AppSettings::maxRetries = 5;
std::cout << AppSettings::maxRetries << '\n';
}
Terminal output:
3
5
No AppSettings object is created. This is the correct way to think about static data members:
You can think of static member variables as global variables that are scoped to a class
However, one quirk is that the static local member variable must be initialized in the global namespace.
Initialization of static member variables inside the class definition
It is desirable to avoid initializing static member variables in the global namespace. Fortunately, their are 3 workarounds.
- Label static members as
const int
class Whatever
{
public:
static const int s_value{ 4 };
// initialization in class permitted
};
2. Label static members as inline (C++17). This means it can be defined in numerous places (This is the preferred and cleanest option of initializing static members)
class AppSettings {
public:
static inline int maxRetries { 3 };
};
3. Use constexpr
#include <iostream>
#include <string_view>
class Limits {
public:
static constexpr int maxConnections { 8 };
static constexpr std::string_view defaultHost { "localhost" };
};
int main()
{
std::cout << Limits::maxConnections << '\n';
std::cout << Limits::defaultHost << '\n';
}
// Terminal output:
8
localhost
Static members have an exclusive, useful feature that non-static members don’t
Type deduction via CTAD and auto.
#include <iostream>
#include <utility>
class BatteryMonitorConfig {
public:
static inline auto samplePeriodMs { 1000 };
static inline std::pair safeVoltageRange { 3.0, 4.2 };
};
int main()
{
std::cout << "Sample period: "
<< BatteryMonitorConfig::samplePeriodMs << " ms\n";
std::cout << "Safe voltage range: "
<< BatteryMonitorConfig::safeVoltageRange.first
<< " V to "
<< BatteryMonitorConfig::safeVoltageRange.second
<< " V\\n";
}
Terminal output:
Sample period: 1000 ms
Safe voltage range: 3 V to 4.2 V
In Conclusion
- Non-static data members are stored per object. They belong to the class.
- Static data members are shared by the class.
- Static data members can be accessed through the class name with
::. E.g.Limits::maxConnections