본문으로 건너뛰기
Previous
Next
C++ static Members: Static Data, Static Functions,

C++ static Members: Static Data, Static Functions,

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 — no this inside)
  • 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 static allows in-class definitions — no separate .cpp entry needed
  • Static member functions have no this — called as Class::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: ++counter is not atomic — use std::atomic<int> or std::mutex for 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 등으로 검색하시면 이 글이 도움이 됩니다.