본문으로 건너뛰기 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", "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 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

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.


Other articles related to this topic.


Keywords Covered in This Article (Related Search Terms)

This article covers C++, static, member, class, OOP.