C++ static Members: Static Data, Static Functions,
이 글의 핵심
Class static members shared by all instances: declaration vs definition, ODR, thread safety, singletons, factories, and C++17 inline static in headers. Complete guide with practical patterns.
What Are Static Members?
Normally, each object of a class has its own copy of member variables. A static member variable is shared by all objects of the class — there is exactly one copy regardless of how many instances exist:
#include <iostream>
class Connection {
static int count_; // shared by all Connection objects
int id_;
public:
Connection() : id_(++count_) {
std::cout << "Connection " << id_ << " opened (total: " << count_ << ")\n";
}
~Connection() {
std::cout << "Connection " << id_ << " closed (total: " << --count_ << ")\n";
}
static int activeCount() { return count_; } // static function
};
// Definition outside the class — required (pre-C++17)
int Connection::count_ = 0;
int main() {
Connection c1; // Connection 1 opened (total: 1)
Connection c2; // Connection 2 opened (total: 2)
{
Connection c3; // Connection 3 opened (total: 3)
} // Connection 3 closed (total: 2)
std::cout << "Active: " << Connection::activeCount() << '\n'; // Active: 2
}
Declaration vs Definition
Static data members must be declared in the class but defined outside (in exactly one .cpp file). This is the One Definition Rule (ODR):
// header: connection.h
class Connection {
static int count_; // declaration — says the member exists
static const int max_; // declaration
public:
static int activeCount();
};
// source: connection.cpp
int Connection::count_ = 0; // definition — allocates storage
const int Connection::max_ = 100; // definition
If you put the definition in the header and include the header in multiple .cpp files, the linker will report a “multiple definition” error.
C++17: inline static
C++17 introduced inline static to allow definitions directly in the class:
// header: config.h — works in C++17 and later
class Config {
public:
inline static int maxConnections = 100;
inline static std::string serverName = "localhost";
static constexpr int version = 3; // constexpr static is implicitly inline
};
// No separate .cpp definition needed
The inline static syntax makes the in-class definition the one official definition — multiple .cpp files that include the header all use the same instance.
Static Member Functions
Static member functions belong to the class, not any instance. They have no this pointer:
#include <string>
#include <iostream>
class Logger {
std::string prefix_;
static int logCount_;
public:
explicit Logger(std::string prefix) : prefix_(std::move(prefix)) {}
// Non-static: operates on an instance
void log(const std::string& msg) {
++logCount_;
std::cout << '[' << prefix_ << "] " << msg << '\n';
}
// Static: no instance needed
static int totalLogs() { return logCount_; }
static void resetCount() { logCount_ = 0; }
};
int Logger::logCount_ = 0;
int main() {
Logger app("APP"), db("DB");
app.log("Started"); // [APP] Started
db.log("Connected"); // [DB] Connected
app.log("Processing"); // [APP] Processing
// Call static function on the class (no object required)
std::cout << "Total logs: " << Logger::totalLogs() << '\n'; // 3
}
Static member functions:
- Can be called as
ClassName::function()without an object - Can also be called on an instance:
obj.staticFunction()(but this is misleading — nothisinside) - Can access private static members
- Cannot access non-static members (no
this)
Singleton Pattern
A singleton ensures only one instance exists. The Meyers singleton (function-local static) is the cleanest modern form:
class AppConfig {
std::string host_;
int port_;
// Private constructor — cannot create directly
AppConfig() : host_("localhost"), port_(8080) {}
public:
// Meyers singleton — thread-safe since C++11
static AppConfig& instance() {
static AppConfig inst; // created once on first call
return inst;
}
// Non-copyable
AppConfig(const AppConfig&) = delete;
AppConfig& operator=(const AppConfig&) = delete;
const std::string& host() const { return host_; }
int port() const { return port_; }
void setPort(int p) { port_ = p; }
};
int main() {
AppConfig::instance().setPort(9090);
// Multiple calls return the same instance
std::cout << AppConfig::instance().host() << ':';
std::cout << AppConfig::instance().port() << '\n'; // localhost:9090
}
The function-local static is guaranteed to be initialized exactly once, and the initialization is thread-safe in C++11 and later.
Static Factory Methods
Static functions are a natural fit for factory methods that create and return instances:
#include <memory>
#include <string>
class User {
std::string name_;
std::string email_;
bool isAdmin_;
// Private constructor
User(std::string name, std::string email, bool isAdmin)
: name_(std::move(name))
, email_(std::move(email))
, isAdmin_(isAdmin) {}
public:
// Named factory methods — clearer than overloaded constructors
static std::unique_ptr<User> createUser(std::string name, std::string email) {
return std::unique_ptr<User>(new User(std::move(name), std::move(email), false));
}
static std::unique_ptr<User> createAdmin(std::string name, std::string email) {
return std::unique_ptr<User>(new User(std::move(name), std::move(email), true));
}
const std::string& name() const { return name_; }
bool isAdmin() const { return isAdmin_; }
};
int main() {
auto user = User::createUser("Alice", "alice@example.com");
auto admin = User::createAdmin("Bob", "bob@example.com");
std::cout << user->name() << " admin=" << user->isAdmin() << '\n'; // Alice admin=0
std::cout << admin->name() << " admin=" << admin->isAdmin() << '\n'; // Bob admin=1
}
Thread Safety for Static Members
Static data members shared across threads need synchronization:
#include <atomic>
#include <mutex>
#include <thread>
#include <iostream>
class EventBus {
// Wrong: ++ is read-modify-write, not atomic
// static int eventCount_;
// Correct: atomic for simple counters
static std::atomic<int> eventCount_;
// For complex state: use mutex
static std::mutex mutex_;
static std::vector<std::string> log_;
public:
static void emit(const std::string& event) {
++eventCount_; // atomic — safe from multiple threads
std::lock_guard<std::mutex> lock(mutex_);
log_.push_back(event);
}
static int count() { return eventCount_.load(); }
};
std::atomic<int> EventBus::eventCount_{0};
std::mutex EventBus::mutex_;
std::vector<std::string> EventBus::log_;
int main() {
std::thread t1([]{ for (int i = 0; i < 100; i++) EventBus::emit("click"); });
std::thread t2([]{ for (int i = 0; i < 100; i++) EventBus::emit("hover"); });
t1.join(); t2.join();
std::cout << "Total events: " << EventBus::count() << '\n'; // 200
}
Initialization Order Problem
Static data members in different translation units have an unspecified initialization order. This can cause the “static initialization order fiasco”:
// a.cpp
int A::value = computeA(); // runs before or after B::value?
// b.cpp
int B::value = computeB(); // unspecified order
// If computeA() reads B::value — B::value might be zero!
Solution: use function-local statics (lazy initialization):
class Registry {
public:
static std::map<std::string, int>& data() {
static std::map<std::string, int> m; // initialized on first call
return m;
}
};
// Always safe — data() initializes on first use, after all global inits
Registry::data()["key"] = 42;
Key Takeaways
- Static data members are shared by all instances — declared in the class, defined in exactly one
.cpp - C++17
inline staticallows in-class definitions — no separate.cppentry needed - Static member functions have no
this— called asClass::function(), cannot access instance members - Meyers singleton (
static local) is thread-safe since C++11 — the standard guarantees once-only initialization - Static factory methods give descriptive names to different object creation paths (better than overloaded constructors)
- Thread safety:
++counteris not atomic — usestd::atomic<int>orstd::mutexfor shared mutable state - Static initialization order: cross-TU initialization order is unspecified — use function-local statics to avoid the fiasco
Frequently Asked Questions (FAQ)
Q. When would I use this in practice?
A. Class static members shared by all instances: declaration vs definition, ODR, thread safety, singletons, factories, and …
Q. What should I read before this?
A. Follow the previous article or related articles links at the bottom of each post to learn in sequence. See the C++ series index for the full picture.
Q. Where can I study this more deeply?
A. Check cppreference and the relevant library’s official documentation. The reference links at the end of the article are also worth using.
Related Articles (Internal Links)
Other articles related to this topic.
- C++ Classes and Objects: Constructors, Access Control, and
- C++ this Pointer: Chaining, const Methods, Lambdas, and CRTP
- C++ friend Keyword: Access Control, Operators, and
Keywords Covered in This Article (Related Search Terms)
This article covers C++, static, member, class, OOP.