C++, static variable in C++ class, initialise, initialize, initialization, initialisation, class variable

Initialise Static Variables in C++ Class (Class Variables)

Originally I only wanted to test a std::uniqe_ptr<T> in C++. I had written a small class for this purpose. While writing it, it occurred to me that an instance variable id (identification) that increments automatically (1, 2, 3, ...) would be quite practical and that a class variable containing the number of currently existing instances of the class could also be useful.

In Python I would do it the following way:

class IdAutoIncrement: __id = 0 # int obj_count = 0 # is ommited, more complicated in Python def __init__(self, name: str): self.__class__.__id += 1 self.__objid = self.__class__.__id self.__name = name def get_id(self): return self.__objid def get_name(self): return self.__name def set_name(self, name: str): self.__name = name; def get_objs_created(self): return self.__class__.__id def __str__(self): return f'name: "{self.get_name()}" id: {self.get_id()}' def main(): aa = IdAutoIncrement('IdAutoIncrement object 0001') print(f'aa: "{str(aa)}"') if __name__ == "__main__": main()

The intention is using a class variable, which will be initialised to 0, when the class will be created. Then in the constructor (__init__()) increment the class variable, and assign it to the instance variable.

So let's implement the above in C++:

#include <iostream> #include <string> #include <memory> // required for std::unique_ptr<...> class ClassIdAutoIncrement { size_t objid; std::string name; static size_t id = 0; static size_t objCount = 0; public: ClassIdAutoIncrement(const std::string &name="NAME_NOT_SET"); ~ClassIdAutoIncrement() { objCount--; } size_t getId() const { return objid; } std::string getName() const { return name; } void setName(std::string name) { this->name = name; } size_t getObjsCreated() { return id; } size_t getObjCount() { return objCount; } }; std::ostream &operator<<(std::ostream &strm, const ClassIdAutoIncrement &cls) { return strm << "name: \"" << cls.getName() << "\" id: " << cls.getId(); } inline ClassIdAutoIncrement::ClassIdAutoIncrement(const std::string &name) { objid = ++id; ++objCount; setName(name); } int main() { auto aa = ClassIdAutoIncrement("ClassIdAutoIncremment Object 0001"); std::cout << aa << std::endl; return 0; }

Now let's run it (e.g. on https://onlinegdb.com). Simply copy the above program to clipboard, paste it and run it. Surprise, result on online.gdb (Compiler C++) is:

main.cpp:9:19: error: ISO C++ forbids in-class initialization of non-const static member ‘ClassIdAutoIncrement::id’ 9 | static size_t id = 0; | ^~ main.cpp:15:19: error: ISO C++ forbids in-class initialization of non-const static member ‘ClassIdAutoIncrement::objCount’ 15 | static size_t objCount = 0; | ^~~~~~~~

Why is this not allowed (possible)? Most likely because the class declaration is normally made in an extra header file, which is included using the ‘#include’ directive. In this case, however, there must be no definition (e.g. of a static variable) in the header file. Otherwise, this variable would be initialized in every C++ code file that includes this header file.

When using inline before static the program will compile with the following warnings:

main.cpp:9:5: warning: inline variables are only available with ‘-std=c++17’ or ‘-std=gnu++17’ 9 | inline static size_t id = 0; | ^~~~~~ main.cpp:10:5: warning: inline variables are only available with ‘-std=c++17’ or ‘-std=gnu++17’ 10 | inline static size_t objCount = 0; | ^~~~~~ name: "ClassIdAutoIncremment Object 0001" id: 1 ...Program finished with exit code 0 Press ENTER to exit console.

Unfortunately there is still a lot of code, which must be compiled with C++ 14 or even C++ 11. If you search on Google for a solution (with e.g. ‘c++ static class variable initialization’), you will often find the solution to initialise static (class) variables outside of the class (and of any function):

#include <iostream> #include <string> #include <memory> // required for std::unique_ptr<...> class ClassIdAutoIncrement { size_t objid; std::string name; static size_t id; // declaration (static) static size_t objCount; // declaration (static) public: ClassIdAutoIncrement(const std::string &name="NAME_NOT_SET"); ~ClassIdAutoIncrement() { objCount--; } size_t getId() const { return objid; } std::string getName() const { return name; } void setName(std::string name) { this->name = name; } size_t getObjsCreated() { return id; } size_t getObjCount() { return objCount; } }; std::ostream &operator<<(std::ostream &strm, const ClassIdAutoIncrement &cls) { return strm << "name: \"" << cls.getName() << "\" id: " << cls.getId(); } inline ClassIdAutoIncrement::ClassIdAutoIncrement(const std::string &name) { objid = ++id; ++objCount; setName(name); } size_t ClassIdAutoIncrement::id = 0; // definition of static class variable id size_t ClassIdAutoIncrement::objCount = 0; // definition of static class variable objCount int main() { auto aa = ClassIdAutoIncrement("ClassIdAutoIncremment Object 0001"); std::cout << aa << std::endl; return 0; }

Is this the solution! Of course no. Assume the class is part of a library, with a separate header file. A user will often forget to initialise the static (class) variables. So here a better solution (the principle can be found on StackOverflow but unfortunately not as the suggested solution, you must scroll down to the answer of ‘no one special’) and also with the use of a std::unique_ptr<>, what was my original intention:

// Alternative for Static Class Variables with Initialisation (WHICH IS NOT POSSIBLE BEFORE C++ 17!) // Copyright (c) 2023, Peter Sulzer Fürth, all rights reserved // This code may be used without any restrictions #include <iostream> #include <string> #include <memory> // required for std::unique_ptr<...> class ClassIdAutoIncrement { size_t objid; std::string name; size_t &id() { // This method normally should only be called ONCE in ctor! static size_t highestId {0}; // using alternative C++ 11 initialisation syntax ({}) return highestId; } size_t &objCount() { // This method normally should only be called once in ctor and once in dtor static size_t objCount {0}; // using alternative C++ 11 initialisation syntax ({}) return objCount; } public: ClassIdAutoIncrement(const std::string &name="NAME_NOT_SET"); ~ClassIdAutoIncrement() { objCount()--; } size_t getId() const { return objid; } std::string getName() const { return name; } void setName(std::string name) { this->name = name; } size_t getObjsCreated() { return id(); } size_t getObjCount() { return objCount(); } }; std::ostream &operator<<(std::ostream &strm, const ClassIdAutoIncrement &cls) { return strm << "name: \"" << cls.getName() << "\" id: " << cls.getId(); } inline ClassIdAutoIncrement::ClassIdAutoIncrement(const std::string &name) { objid = ++id(); ++objCount(); setName(name); } int main() { std::cout << "Instance aa on stack:\n"; auto aa = ClassIdAutoIncrement("ClassIdAutoIncrement object 1001"); std::cout << aa << " number of ClassIdAutoIncrement objects: " << aa.getObjCount() << "\n"; { std::cout << "Instance bb in main on stack inside of scope { ... }:\n"; auto bb = ClassIdAutoIncrement("ClassIdAutoIncrement object 1002"); std::cout << bb << " number of ClassIdAutoIncrement objects: " << bb.getObjCount() << "\n"; } std::cout << "now bb has been deleted/destroyed (goes out of scope of { ... })\n"; std::cout << "number of ClassIdAutoIncrement objects: " << aa.getObjCount() << "\n"; std::cout << "Instance cc in stack:\n"; auto cc = ClassIdAutoIncrement("ClassIdAutoIncrement object 1003"); std::cout << cc << " number of ClassIdAutoIncrement objects: " << aa.getObjCount() << "\n"; ClassIdAutoIncrement *ddBak; // Needed for demonstration of pitfalls with a raw pointer { std::cout << "Instance pointed to by raw pointer dd in main() in heap in scope { ... }:\n"; auto *dd = new ClassIdAutoIncrement("ClassIdAutoIncrement object 1004"); ddBak = dd; // Lets make a backup copy for demonstration std::cout << *dd << " number of ClassIdAutoIncrement objects: " << dd->getObjCount() << "\n"; } std::cout << "Oops, if we didn't have a backup copy of dd we would have a memory leak(!) Proof:\n"; std::cout << "number of ClassIdAutoIncrement objects: " << aa.getObjCount() << "\n"; std::cout << "Fortunately we have made a backup copy for pointer dd, lets delete it.\n"; delete ddBak; std::cout << "Now instance in heap pointed to by dd has been deleted/destroyed\n"; std::cout << "number of ClassIdAutoIncrement objects: " << aa.getObjCount() << "\n"; { std::cout << "Now create instance in main() in heap pointed to by dd in scope { ... } with an unique_ptr\n"; std::unique_ptr<ClassIdAutoIncrement> dd { new ClassIdAutoIncrement("ClassIdAutoIncrement object 1005") }; std::cout << *dd << " number of ClassIdAutoIncrement objects: " << dd->getObjCount() << "\n"; } std::cout << "Albeit we are out of scope { ... } where dd has had been declared, we have no memory leak:\n"; std::cout << "number of ClassIdAutoIncrement objects: " << aa.getObjCount() << "\n"; return 0; }

The trick is to initialise a static variable in a method (which is allowed) which returns a reference to this variable. In our example the (private :-)) methods size_t &id() and size_t &objCount(). The initialisation (which could also be written old style with e.g. static size_t highestId = 0;) occurs only at the first call to the method (it's an initialisation, not an assignment, which are two completly different things in C++).

In the constructor (ctor) or destructor (dtor) of our class, we then can simply call these methods and as they return a reference to a static variable, pre- or post-increment (or decrement) these method calls, e.g. in dtor with objCount()--;.