Static Member Variables in C++

We introduced global variables, and static local variables. Both of these types of variables have static duration, meaning they are created at the start of the program, and destroyed at the end of the program. Such variables keep their values even if they go out of scope.

#include <iostream>

int generateID()
{
    static int s_id{ 0 }; // static local variable
    return ++s_id;
}

int main()
{
    std::cout << generateID() << '\n';
    std::cout << generateID() << '\n';
    std::cout << generateID() << '\n';

    return 0;
}

This program prints:

1
2
3

Note that static local variable s_id has kept its value across multiple function calls.

Class types brings two more uses for the static keyword:

  • Static member variables
  • Static member functions

Static member variables

Before we go into the static keyword as applied to member variables, first consider the following class:

#include <iostream>

struct Something
{
    int value{ 1 };
};

int main()
{
    Something first{};
    Something second{};

    first.value = 2;

    std::cout << first.value << '\n';
    std::cout << second.value << '\n';

    return 0;
}

When we instantiate a class object, each object gets its own copy of all normal member variables. In this case, because we have declared two Something class objects, we end up with two copies of value:first.value, and second.value.first.value is distinct from second.value. Consequently, the program above prints:

2
1

Member variables of a class can be made static by using the static keyword. Unlike normal member variables, static member variables are shared by all objects of the class. Consider the following program, similar to the above:

#include <iostream>

struct Something
{
    static int s_value; // now static
};

int Something::s_value{ 1 }; // initialize s_value to 1

int main()
{
    Something first{};
    Something second{};

    first.s_value = 2;

    std::cout << first.s_value << '\n';
    std::cout << second.s_value << '\n';
    return 0;
}

This program prints the following:

2
2

Because s_value is a static member variable, s_value is shared between all objects of the class. Consequently, first.s_value is the same variable as second.s_value. The above program shows that the value we set using first can be accessed using second.

Static members are not associated with class objects

Although you can access static members through objects of the class (as shown with first.s_value and second.s_value in the example above), static members exist even if no objects of the program and destroyed at the end of the program, so their lifetime is not bound to as class object like a normal member.

Essentially, static members are global variables that live inside the scope region of the class. There is very little difference between a static member of a class and a normal inside a namespace.

Because static member s_value exists independently of any class objects, it can be accessed directly using the class name and the scope resolution operator (in this case Something::s_value):

class Something
{
public:
    static int s_value; // declares the static member variable
};

int Something::s_value{ 1 }; // defines the static member variable (we'll discuss this section below)

int main()
{
    // note: we're not instantiating any objects of type Something

    Something::s_value = 2;
    std::cout << Something::s_value << '\n';
    return 0;
}

In the above snipped, s_value is referenced by class name Something rather than through an object. Note that we have not even instantiated an object of type Something, but we are still able to access and use Something::s_value. This is the preferred method for accessing static members.

Defining and initializing static member

When we declare a static member variable inside a class type, we are telling the compiler about the existence of a static member variable, but not actually defining it (much like forward declaration). Because static member variables are essentially global variables, you must explicitly define (and optionally initialize) the static member outside of the class, in the global scope.

In the example above, we do so via this line:

int Something::s_value{ 1 }; // defines the static member variable

This line serves two purposes:

  • It instantiates the static member variable (just like a global variable), and initializes it. In this case, we are providing the initialization value 1. If no initializer is provided, static member variables are zero-initialized by default.

Note that this static member definition is not subject to access controls: You can define and initialize the value even if it's declared as private (or protected) in the class.

If the class is defined in a header (.h) file, the static member definition is usually placed in the associated code file for the class (e.g. Something.cpp). If the class is undefined in a source (.cpp) file, the static member definition is usually placed directly underneath the class. Do not put the static member definition in a header file (much like a global variable, if that header file get included more than once, you will end up with multiple definitions, which will cause a compile error).

Initialization of static member variables inside the class definition:

There are a few shortcuts to the above.

First, when the static member is a constant integral types (which includes char and bool) or a const enum, the static member can be initialized inside the class definition:

class Whatever
{
public:
    static const int s_value{ 4 }; // a static const int can be defined and initialized directly
};

In the above example, because the static member variable or is a const int, no explicit definition line is needed.

An example of static member variables

Why use static variables inside classes? One use is to assign a unique ID to every instance of the class. Here's an example:

#include <iostream>

class Something
{
private:
    static inline int s_idGenerator { 1 };
    int m_id {};

public:
    Something() { m_id = s_idGenerator++; } // grab the next value from the id generator

    int getID() const { return m_id; }
};

int main()
{
    Something first{};
    Something second{};
    Something third{};

    std::cout << first.getID() << '\n';
    std::cout << second.getID() << '\n';
    std::cout << third.getID() << '\n';
    return 0;
}

This program prints:

1
2
3

Because s_idGenerator is shared by all Something objects, when a new Something object is created, the constructor initializes m_id witht the current value of s_idGenerator and then increments the value for the next object. This guarantees that each instantiated Something object receives a unique id (incremented in the order of creation).

Static Member Functions

If a static member variable is public, it can be accessed directly using the class name and the scope resolution operator:

#include <iostream>

class Something
{
public:
    static inline int s_value { 1 };
};

int main()
{
    std::cout << Something::s_value; // s_value is public, we can access it directly
}

But what if a static member variable is private?

#include <iostream>

class Something
{
private: // now private
    static inline int s_value { 1 };
};

int main()
{
    std::cout << Something::s_value; // error: s_value is private and can't be accessed directly outside the class
}

In this case, we can't access Something::s_value directly from main(), because it is private. Normally we access private members through public member functions. While we could create a normal public member function to access s_value, we would then instantiate an object of the class type to use the function.

#include <iostream>

class Something
{
private:
    static inline int s_value { 1 };

public:
    int getValue() { return s_value; }
};

int main()
{
    Something s{};
    std::cout << s.getValue(); // works, but requires us to instantiate an object to call getValue()
}

Static Member Functions

Member variables aren't the only type of member that can be made static. Member functions can be made static as well. Here is the above example with a static member function accessor:

#include <iostream>

class Something
{
private:
    static inline int s_value { 1 };

public:
    static int getValue() { return s_value; } // static member function
};

int main()
{
    std::cout << Something::getValue() << '\n';
}

Because static member functions are not associated with a particular object, they can be called directly by using the class name and the scope resolution operator (e.g., Something::getValue()). Like static member variables, they can also be called through objects of the class type, though this is not recommended.

Static Member Function have no *this pointer

Static member functions have two interesting quirks worth noting.

  • First, because static member functions are not attached to an object, they have no this pointer. This makes sense when you think about it – the this pointer always points to the object that the member function is working on. Static member function do not work on an object, so the this pointer is not needed.
  • Second, static member functions can directly access other static members (variables or functions), but not non-static members. This is because non-static members must belong to a class object, and static member functions have no class object to work with.

Another example:

Static member functions can also be defined outside of the class declaration. This works the same way as for normal member functions:

#include <iostream>

class IDGenerator
{
private:
    static inline int s_nextID { 1 };

public:
     static int getNextID(); // Here's the declaration for a static function
};

// Here's the definition of the static function outside of the class.  Note we don't use the static keyword here.
int IDGenerator::getNextID() { return s_nextID++; }

int main()
{
    for (int count{ 0 }; count < 5; ++count)
        std::cout << "The next ID is: " << IDGenerator::getNextID() << '\n';

    return 0;
}

This program prints:

The next ID is: 1
The next ID is: 2
The next ID is: 3
The next ID is: 4
The next ID is: 5

Note that because all the data and functions in this class are static, we don't need to instantiate an object of the class to make use of its functionality. This class utilizes a static member variable to hold of the next ID to be assigned, and provides a static member function to return that ID and increment it.

C++ does not support static constructors

If you can initialize a normal member variables via a constructor, then by extension it makes sense that you should be able to initialize static member variables via a static constructor. C++ does not support it.

If your static variable can be directly initialized, no constructor is needed: You can initialize the static member variable at the point of definition (even if it is private).

#include <array>
#include <iostream>

struct MyClass
{
	static inline std::array s_mychars { 'a', 'e', 'i', 'o', 'u' }; // initialize static variable at point of definition
};

int main()
{
    std::cout << MyClass::s_mychars[2]; // print i
}

// Output
i

If initializing your static member variable requies executing code (e.g a loop), there are many different, somewhat obtuse ways of doing this. One way that works with all variables, static or not, is to use a function to create an object, fill it with data, and return it to the caller. This returned value can be copied into the object being initialized.

#include <array>
#include <iostream>

class MyClass
{
private:
    static std::array<char, 5> generate()
    {
        std::array<char, 5> arr; // create an object
        arr[0] = 'a'; // fill it with values however you like
        arr[1] = 'e';
        arr[2] = 'i';
        arr[3] = 'o';
        arr[4] = 'u';

        return arr; // return the object
    }

public:
	static inline std::array s_mychars { generate() }; // copy the returned object into s_mychars
};

int main()
{
    std::cout << MyClass::s_mychars[2]; // print i
}