I hope it will be not the last post in that blog. Under the heading "Games" I am going to talk about things that you have always wanted to try, but because of laziness or high employment always were afraid to do it. So, let's play!
Today I want to play with static data in C++.
The theoretical conception of static data, and how it should be used can be obtained by reading the first N links on request static keyword [C++]. So, lets move to practice right now.
In my experiments I will use simple class, that will print string in its constructor and destructor. Printed string is passed as argument for constructor:
#include <stdio.h> class CThing { CThing(); const char *const m_name; public: CThing(const char *const name): m_name(name) { printf("CThing::CThing(\"%s\")\n", name); } ~CThing() { printf("CThing::~CThing(\"%s\")\n", m_name); } void echo() const { printf("echo from \"%s\"\n", m_name); }For the beginning, lets figure out when constructor and destructor of a static variables are called:
void someProc1() { static CThing thing("thing in someProc1"); } CThing thing1("thing1"); int main(int argc, char *argv[]) { (void)argc; (void)argv; printf("---- main() ----\n"); someProc1(); someProc1(); printf("---- return ----\n"); return 0; }After executing the program:
CThing::CThing("thing1") ---- main() ---- CThing::CThing("thing in someProc1") ---- return ---- CThing::~CThing("thing in someProc1") CThing::~CThing("thing1")As you can see, "thing in someProc1" actually created at the time of the first call to someProc1(). Destructors as it should be, are called in reverse order of constructor calls. Some people do not fully understand how static variables work, and therefore give the compiler different magical abilities. In fact, there is no magic of course. Static variable definition:
void someProc1() { static CThing thing("thing"); }compiler will translate into something like this:
bool g_thingInitialized = false; CThing *g_thingPtr; void g_thingDelete() { delete g_thingPtr; } void someProc1() { if (!g_thingInitialized) { g_thingInitialized = true; g_thingPtr = new CThing("thing"); atexit(g_thingDelete); } }Like that easy. And because of such a simple realization of static variables, there are two issues (which are described in detail in C++ scoped static initialization is not thread-safe, on purpose!):
- Contrary to popular opinion, the fact that the static variable is initialized will be checked each time function is called.
- It is not thread-safe to use static variables. The compiler does not know anything about threads and does not provide any synchronization when initializing static variables. Thus, multiple instances of the class CThing can be created (plus a whole bunch of troubles, which you can get, hoping that all synchronization work will be done by compiler).
class CBigThing { static CThing m_thing; const char *const m_name; public: CBigThing(const char *const name): m_name(name) { printf("CBigThing::CBigThing(\"%s\")\n", name); } ~CBigThing() { printf("CBigThing::~CBigThing(\"%s\")\n", m_name); } }; class CAnyThing { static CThing m_thing; const char *const m_name; public: CAnyThing(const char *const name): m_name(name) { printf("CAnyThing::CAnyThing(\"%s\")\n", name); } ~CAnyThing() { printf("CAnyThing::~CAnyThing(\"%s\")\n", m_name); } }; CThing CAnyThing::m_thing("m_thing from CAnyThing"); CThing thing1("thing1"); CThing CBigThing::m_thing("m_thing from CBigThing"); int main(int argc, char *argv[]) { (void)argc; (void)argv; printf("---- main() ----\n"); CBigThing bigThing("bigThing"); printf("---- return ----\n"); return 0; }Program will print:
CThing::CThing("m_thing from CAnyThing") CThing::CThing("thing1") CThing::CThing("m_thing from CBigThing") ---- main() ---- CBigThing::CBigThing("bigThing") ---- return ---- CBigThing::~CBigThing("bigThing") CThing::~CThing("m_thing from CBigThing") CThing::~CThing("thing1") CThing::~CThing("m_thing from CAnyThing")As you can see, static class members are not created with the first instance of a class (as one would assume). They are created with other global variables in the order of their definition (and destroyed in the reverse order).
It is worth noting that the order of initialization of global variables exactly known only for variables defined in the scope of one file (module). The order of initialization of global variables in different files is not defined and depends on factors, which you probably do not want to rely on. Here is a good example of what can happen if you carelessly use global variables within different files:
// it CAN be in file actions.cpp: extern CThing thingA; extern CThing thingB; bool doThings() { thingA.echo(); thingB.echo(); return true; } bool doneThings = doThings(); // it CAN be in file things.cpp: CThing thingA("thingA"); CThing thingB("thingB"); // it CAN be in file main.cpp: int main(int argc, char *argv[]) { (void)argc; (void)argv; printf("---- main() ----\n"); printf("---- return ----\n"); return 0; }Output will be:
echo from "(null)" echo from "(null)" CThing::CThing("thingA") CThing::CThing("thingB") ---- main() ---- ---- return ---- CThing::~CThing("thingB") CThing::~CThing("thingA")Objects thingA and thingB where used uninitialized. When the code is located in a single file, the reason of such behavior is obvious - doneThings is defined before thingA and thingB. However, if you split the code on files actions.cpp, things.cpp and main.cpp (as indicated in the comments), then the situation becomes more interesting. The program can begin to work as required and print:
CThing::CThing("thingA") CThing::CThing("thingB") echo from "thingA" echo from "thingB" ---- main() ---- ---- return ---- CThing::~CThing("thingB") CThing::~CThing("thingA")But maybe not. Depends on the implementation of the compiler used (its version, the name of source files, the order in which files specified in the compiler command line, ...). I guess no one wants to have a program that can stop working after adding a new file or renaming the existing one.
Ok, I thing it's enough for beginning :)
No comments:
Post a Comment