Search code examples
c++classstaticinstancelanguage-design

Why are method-local static variables bound to class and not to instances?


In this class

struct A
{
    ...
    void method()
    {
        static x=0;
        x++;
        ...
    }
}

for each instance of A, a call to method() will increment x for all instances.

I expected x to be incremented for the instance in which method() is called only, and not to affect x for any other instance. This effectively binds the method-local static variable to the class, and as a side issue: why can I not have class-level static variables (only const's), which I would expect to behave as the method-local static variable does currently.

I know I can 'fix' this with some extra code, but still want to understand the reason for this behavior.

Some code for those who want to see the behavior:

#include <iostream>
#include <string>
#include <sstream>
#include <vector>

inline void PRINTSTRING(const std::string &s) { std::cout << s; std::cout.flush(); }
template<typename...T> void say(T...t) { std::stringstream ss{}; (ss<<...<<t); PRINTSTRING(ss.str()); }
template<typename...T> std::string says(T...t) { std::stringstream ss{}; (ss<<...<<t); return ss.str(); }
template<typename...T> bool sayerr(T...t) { say("Error: ", t...); return false; }

struct A { std::string sa{"A"}; void who() { static int a=0; a++; say(says(sa, " a=", a, "\n")); }};

std::vector<A*> AList{};

void killas() { while (!AList.empty()) { auto it=AList.begin(); delete (*it); AList.erase(it); }}
A* newa(const std::string &s) { A *pA=new A; if (pA) { pA->sa=s; AList.push_back(pA); } return pA; }
void showas() { if (AList.empty()) say("-empty-\n"); else for (auto p:AList) p->who(); }

int main(int argc, const char *argv[])
{
    say("\ntesting if a static var in a method is bound to instance or to class ...\n\nexpect 'empty'\n");
    showas();
    newa("one"); newa("two"); newa("three"); newa("four"); newa("five");
    say("\nif bound to instance expect all 1's\n");
    showas();
    say("\nif bound to instance expect all 2's\n");
    showas();
    killas();
    return 0;
}

Solution

  • Found this that, while not explaining the reason for the behavior, does give a good description of it's uses.

    Also regarding storage duration it says:

    Variables declared at block scope with the specifier static or thread_local
    (since C++11) have static or thread (since C++11) storage duration but are
    initialized the first time control passes through their declaration (unless their
    initialization is zero- or constant-initialization, which can be performed before
    the block is first entered). On all further calls, the declaration is skipped.

    and a little further on:

    Function-local static objects in all definitions of the same inline function
    (which may be implicitly inline) all refer to the same object defined in one
    translation unit.

    I was not aware of this and knowing it now will certainly help to prevent unintended 'features' in my code.