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", "[email protected]");
auto admin = User::createAdmin("Bob", "[email protected]");
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
자주 묻는 질문 (FAQ)
Q. 이 내용을 실무에서 언제 쓰나요?
A. Class static members shared by all instances: declaration vs definition, ODR, thread safety, singletons, factories, and … 실무에서는 위 본문의 예제와 선택 가이드를 참고해 적용하면 됩니다.
Q. 선행으로 읽으면 좋은 글은?
A. 각 글 하단의 이전 글 또는 관련 글 링크를 따라가면 순서대로 배울 수 있습니다. C++ 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.
Q. 더 깊이 공부하려면?
A. cppreference와 해당 라이브러리 공식 문서를 참고하세요. 글 말미의 참고 자료 링크도 활용하면 좋습니다.
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- [C++ Classes and Objects: Constructors, Access Control, and](/en/blog/cpp-class-object-beginner/
- [C++ this Pointer: Chaining, const Methods, Lambdas, and CRTP](/en/blog/cpp-this-pointer/
- [C++ friend Keyword: Access Control, Operators, and](/en/blog/cpp-friend-keyword/
이 글에서 다루는 키워드 (관련 검색어)
C++, static, member, class, OOP 등으로 검색하시면 이 글이 도움이 됩니다.