Skip to content
C++
Language
since C++98
Intermediate

Static Members

Static data members and static member functions belong to the class type itself, not to any instance, and are shared across all objects of that class.

Static Memberssince C++98

A static member of a class β€” whether a data member or a member function β€” belongs to the class itself rather than to any individual object instance, meaning there is exactly one copy shared across all objects and the member can be accessed without an object.

Overview

Static members come in two forms: static data members and static member functions. Both are declared inside the class body using the static keyword, but neither is tied to a particular instance.

Static data members have static storage duration. Regardless of how many objects of the class exist β€” even zero β€” there is exactly one copy of each static data member in the program. They are initialized before main is entered and destroyed after it returns.

Static member functions have no implicit this parameter. They can be called through the class name without an object, and they can only directly access other static members of the class (they have no handle to any instance's non-static data).

Access control (public, protected, private) applies to static members exactly as it does to non-static members.

Syntax

cpp
class Foo {
public:
    static int counter;             // declaration of static data member
    static void reset();            // declaration of static member function

    static constexpr int kMax = 100; // C++11: constexpr integral constant, defined inline
};

// Out-of-class definition required (pre-C++17) for non-constexpr static data members
int Foo::counter = 0;

void Foo::reset() {
    counter = 0;
}

Since C++17, you can define a static data member inline inside the class body using the inline specifier, eliminating the need for a separate out-of-class definition:

cpp
class Bar {
public:
    inline static int counter = 0; // C++17: definition lives here
};

Prior to C++17, only static data members of const integral or enumeration type could be initialized inside the class body (as a convenience for compile-time constants), but they still required an out-of-class definition if odr-used:

cpp
class Baz {
    static const int kBufSize = 256; // C++98/03: in-class initialization for const integral types
};
const int Baz::kBufSize; // Still needed if odr-used before C++17

Examples

Instance counting

A classic use of static data members is tracking how many live instances of a class exist:

cpp
#include <iostream>

class Connection {
public:
    Connection() { ++active_count_; }
    ~Connection() { --active_count_; }

    static int active_count() { return active_count_; }

private:
    inline static int active_count_ = 0; // C++17
};

int main() {
    std::cout << Connection::active_count() << '\n'; // 0

    Connection c1, c2;
    std::cout << Connection::active_count() << '\n'; // 2

    {
        Connection c3;
        std::cout << Connection::active_count() << '\n'; // 3
    }
    std::cout << Connection::active_count() << '\n'; // 2
}

Static factory and class-level utilities

Static member functions are the right tool when an operation is conceptually part of a class but does not need instance data:

cpp
#include <string>
#include <optional>

class Token {
public:
    enum class Kind { Identifier, Number, Symbol };

    Token(std::string value, Kind kind) : value_(std::move(value)), kind_(kind) {}

    static std::optional<Token> parse(std::string_view src); // C++17: string_view, optional

    Kind kind() const { return kind_; }
    const std::string& value() const { return value_; }

private:
    std::string value_;
    Kind kind_;
};

std::optional<Token> Token::parse(std::string_view src) {
    if (src.empty()) return std::nullopt;
    if (std::isdigit(static_cast<unsigned char>(src[0])))
        return Token{std::string(src), Kind::Number};
    return Token{std::string(src), Kind::Identifier};
}

Static members in class templates

Each distinct template specialization gets its own copy of every static member:

cpp
template <typename T>
struct Registry {
    inline static int instance_count = 0; // C++17

    Registry()  { ++instance_count; }
    ~Registry() { --instance_count; }
};

// Registry<int>::instance_count and Registry<double>::instance_count are independent variables

Calling static member functions

Static member functions can be called through the class name, through an object, or through a pointer β€” all three are equivalent:

cpp
struct Logger {
    static void log(const char* msg);
};

Logger l;
Logger* p = &l;

Logger::log("via class name"); // preferred
l.log("via object");           // legal but misleading
p->log("via pointer");         // also legal

The object/pointer forms are legal because the compiler simply ignores the object β€” they produce identical code.

Best Practices

Use inline static (C++17) for data members. Defining static data members in the class body removes the fragile split between declaration and out-of-class definition, eliminates ODR violations when headers are included in multiple translation units, and co-locates the initializer with the declaration.

Prefer static constexpr for compile-time constants. Since C++11, static constexpr data members are implicitly inline (since C++17) and usable in constant expressions without needing a separate definition. This supersedes the old const integral trick.

cpp
struct Limits {
    static constexpr std::size_t kMaxSize = 1024; // C++11, implicitly inline in C++17
};

Use static member functions for class-scoped utilities that need access to private types or implementation details, but do not need instance state. They are preferable to free functions when the operation is intrinsically about the class (e.g., factory methods, type-level queries) and needs access to private members.

Avoid static mutable data that is not explicitly guarded. Static data members with mutable state are shared program-wide, which creates the same threading and coupling problems as global variables. If a static member must be mutated from multiple threads, protect it with a std::mutex or use std::atomic.

Common Pitfalls

Forgetting the out-of-class definition (pre-C++17)

The most common error before C++17 is declaring a static data member in the class but omitting its definition. The linker will report an undefined symbol:

cpp
// header.h
class Foo {
    static int count; // declaration only
};

// Without this in exactly one .cpp, linking fails:
// int Foo::count = 0;

The fix for new code is inline static int count = 0; inside the class body (C++17+).

Static initialization order fiasco

The order in which static data members in different translation units are initialized is unspecified. Relying on one TU's static being initialized before another TU's can produce undefined behaviour:

cpp
// a.cpp
int A::value = 42;

// b.cpp β€” may run before a.cpp initializes A::value
int B::derived = A::value * 2; // undefined if A::value not yet initialized

The standard fix is the construct-on-first-use idiom: wrap the value in a static local inside a static member function, leveraging the guarantee that function-local statics are initialized on first call (since C++11, this initialization is also thread-safe):

cpp
class Config {
public:
    static Config& instance() {
        static Config cfg; // C++11: thread-safe initialization
        return cfg;
    }
private:
    Config() = default;
};

Local classes cannot have static data members

A class defined inside a function body cannot have static data members β€” there is no valid scope in which to write the out-of-class definition:

cpp
void f() {
    class Local {
        // static int x; // ill-formed
    };
}

If you need class-scoped shared state inside a function, use a function-local static variable outside the class, or refactor to a named class at namespace scope.

operator new and operator delete are implicitly static

Class-specific operator new and operator delete overloads are always static member functions, even when declared without the static keyword. This is mandated by the standard: they operate on raw memory before any object exists (or after it is destroyed), so they cannot have a meaningful this.

Inline Static Members (C++17)

inline static moves the definition into the class body, ending the two-file dance. Before C++17, every non-constexpr static data member had to be defined in exactly one translation unit; the class body held only a declaration. inline collapses declaration and definition into one place, and the linker merges all copies from different TUs into one.

cpp
// C++17
class Widget {
public:
    inline static int instance_count = 0;          // definition β€” no .cpp entry needed
    inline static constexpr int kVersion = 3;      // compile-time constant, also self-contained
};

The pre-C++17 equivalent required two separate pieces that had to stay in sync:

cpp
// C++11 / C++14 β€” header
class Widget {
public:
    static int instance_count;                     // declaration only
    static constexpr int kVersion = 3;             // ok inside class for constexpr integral
};

// exactly one .cpp
int Widget::instance_count = 0;                    // definition

inline static constexpr is the preferred idiom for compile-time class constants in C++17 and later: it is simultaneously a constant expression, has no ODR footgun, and requires no out-of-class definition.

static constexpr vs inline static constexpr

The distinction matters the moment you ODR-use the constant. A static constexpr member declared in C++11/14 style is usable in constant expressions, but if the program takes its address or binds it to a reference the linker requires an out-of-class definition β€” one that carries no initializer.

cpp
// C++11
struct Limits {
    static constexpr int N = 42;                   // declaration + in-class initializer
};

constexpr int Limits::N;                           // C++11: out-of-class definition required if odr-used

void odr_use() {
    const int* p = &Limits::N;                     // odr-use: takes address β€” needs definition above
    int arr[Limits::N];                            // NOT odr-use: used in constant expression β€” fine
}

In C++17 static constexpr inside a class body is implicitly inline, so the out-of-class definition is no longer needed. Spelling it inline static constexpr makes the intent explicit and works in both C++17 and later:

cpp
// C++17
struct Limits {
    inline static constexpr int N = 42;            // self-contained; safe to odr-use
};

void safe_odr_use() {
    const int* p = &Limits::N;                     // fine β€” definition is already here
    std::array<int, Limits::N> arr{};              // also fine as template argument
}

The practical rule: in C++17+ always write inline static constexpr; in C++11/14 avoid odr-using static constexpr members or add the out-of-class definition.

Static Member Functions: No this, No Virtual

A static member function has no implicit this parameter, so it can only access static members and globals β€” but it is otherwise a first-class callable. It can be passed as a function pointer, used in std::call_once for lazy initialization, and serves as a natural factory entry point.

cpp
#include <mutex>

class Logger {
public:
    // Factory pattern: construction logic belongs to the class but needs no instance
    static Logger& get() {
        static Logger instance;                    // C++11: magic static, thread-safe
        return instance;
    }

    static void set_level(int level) {
        level_ = level;                            // accesses only static member
    }

    void write(const char* msg) const;             // non-static: needs this

private:
    Logger() = default;
    inline static int level_ = 0;                 // C++17
};

Static member functions cannot be declared const, volatile, or virtual β€” those qualifiers all modify the implicit this, which does not exist. They also cannot be override or final. They can be called through an object or pointer (the object is silently ignored), but calling through the class name is clearer:

cpp
Logger::set_level(2);          // preferred
Logger::get().set_level(2);    // also fine β€” get() returns a reference, set_level is static

std::call_once pairs naturally with a static member function when one-time initialization must be explicitly thread-guarded rather than relying on magic statics:

cpp
#include <mutex>

class Config {
public:
    static void init() {
        std::call_once(init_flag_, []{ loaded_ = true; }); // C++11
    }
private:
    inline static bool loaded_  = false;          // C++17
    inline static std::once_flag init_flag_{};    // C++17
};

Thread Safety of Static Local Variables vs Static Members

C++11 introduced a key asymmetry: function-local statics are thread-safe on first use; class-level statics are not automatically so. The standard guarantees that if multiple threads race to initialize a static local variable, exactly one runs the initializer and the rest wait β€” this is the "magic static" or "Meyers singleton" guarantee.

cpp
// C++11 β€” thread-safe lazy initialization via magic static
class Registry {
public:
    static Registry& instance() {
        static Registry reg;  // initialized once, safely, even under concurrency
        return reg;
    }
private:
    Registry() = default;
};

Class-level static data members obey a different rule: within a single translation unit, statics are initialized in declaration order before main; across translation units the order is unspecified. This is the Static Initialization Order Fiasco (SIOF):

cpp
// a.cpp
int A::seed = compute_seed();   // may run after b.cpp if linker orders TUs arbitrarily

// b.cpp
int B::value = A::seed * 2;    // undefined behaviour if A::seed not yet initialized

The Construct On First Use idiom eliminates SIOF by replacing the static data member with a static local inside a static member function, trading the unspecified cross-TU order for the well-defined magic-static guarantee:

cpp
// C++11
class Config {
public:
    static int& seed() {
        static int s = compute_seed();   // initialized on first call, thread-safe
        return s;
    }
};

// Anywhere else:
int B::value = Config::seed() * 2;      // safe: seed() initializes on demand

For plain mutable static data members shared across threads, use std::atomic<T> for lock-free access or a std::mutex for compound operations.

Static Members in Templates

Each distinct template instantiation gets its own independent copy of every static member. Foo<int>::counter and Foo<double>::counter are separate variables with separate storage, even though they come from the same template.

cpp
// C++17
template <typename T>
class InstanceCounter {
public:
    InstanceCounter()  { ++count_; }
    ~InstanceCounter() { --count_; }

    static int live() { return count_; }

private:
    inline static int count_ = 0;   // one variable per instantiation
};

// Usage
InstanceCounter<int>    a, b;       // InstanceCounter<int>::count_    == 2
InstanceCounter<double> x;          // InstanceCounter<double>::count_  == 1
// The two counts are completely independent

A static constexpr member inside a template class can itself be used as a template argument, since it is a constant expression:

cpp
// C++11
template <typename T>
struct Traits {
    static constexpr std::size_t alignment = alignof(T);
};

template <std::size_t N>
struct Aligned {};

using AlignedInt = Aligned<Traits<int>::alignment>;   // ok: constant expression

The out-of-class definition rule follows the same pattern as non-template classes, except the definition must be written as a template:

cpp
// C++11/14 out-of-class definition for a template static member
template <typename T>
int InstanceCounter<T>::count_ = 0;    // one definition, instantiated per T

With inline static (C++17) this separate definition is unnecessary.

Mutable Static State Anti-Pattern

Global mutable state hidden inside a class is still global mutable state. Class-level static data members share every problem of global variables β€” unpredictable initialization order, difficult test isolation, and implicit coupling between otherwise unrelated code β€” with the added confusion that they look like instance state at the call site.

The Meyer's singleton is the acceptable exception: it is effectively immutable after first use and its initialization order is deterministic via Construct On First Use:

cpp
// C++11 β€” acceptable: Meyer's singleton, thread-safe, no SIOF
class EventBus {
public:
    static EventBus& get() {
        static EventBus bus;
        return bus;
    }
    void publish(int event);
private:
    EventBus() = default;
};

Contrast with the anti-pattern of a globally mutable counter or registry that every component writes to:

cpp
// Anti-pattern: hidden global state, breaks test isolation
class RequestHandler {
public:
    void handle() { ++total_requests_; }           // side-effects invisible to callers
    static int total_requests_;
};
int RequestHandler::total_requests_ = 0;           // all tests share this

The preferred alternative is dependency injection: pass the shared state explicitly so callers control lifetime and tests can substitute it:

cpp
// Better: explicit dependency, testable
class Stats {
public:
    void record_request() { ++count_; }
    int count() const { return count_; }
private:
    int count_ = 0;
};

class RequestHandler {
public:
    explicit RequestHandler(Stats& stats) : stats_(stats) {}
    void handle() { stats_.record_request(); }
private:
    Stats& stats_;
};

Static state also prevents parallelism: two threads cannot each run their own isolated RequestHandler when both write to the same static counter. Dependency injection or thread-local storage are the clean remedies.

See Also