I've got a class (A) that accesses (indirectly via a static method) a static variable (an STL container) in another class (B) in its constructor and destructor.
A objects may be global, global constants, static members of another class, stored in other classes (which may themselves have global or static instances) or basically anywhere else a c++ object can be.
If an A object is constructed before the static members in B or destructed after the static members in B, it will cause a crash a开发者_如何学Ct some point (usually an access violation).
Is there some way to guarantee that all instances of class A (except those that have leaked, since by definition there "lost" and so wont be destructed any way) are constructed after and destructed before B's static variable?
I've seen some solutions for making a specific variable be constructed/destructed before/after another, however not a general case of all instances of a given type so am not sure how to approach this.
No. This is known as the static-initialization fiasco. The order that objects get constructed prior to entering main is unspecified. The only guarantee is that it happens.
What you can do is lazy-initialize. This means your objects won't be initialized until you use them. Such as:
struct A { /* some data */ };
struct B { B(void){ /* get A's data */ } };
A& get_A(void)
{
static A instance;
return instance;
}
B& get_B(void)
{
static B instance;
return instance;
}
You use get_A
and get_B
to get the global instances. The part where B
uses A
should use get_A
, and your use of B
should be with get_B
. Note the get_B
is optional in your case.
What happens when B is first created? (Either globally or in the function) The constructor will call get_A
and that's where A
will be created. This let's you control the order things get constructed.
Note I think I reversed your A and B.
In general, no such method. There are workarounds, though. You can get an object with global scope and a slightly-less-than global lifetime by having a global pointer and initializing/destructing it in main/WinMain. Also, you place your global state to be destructed last in a ref-counted heap object.
Also, consider redesign :)
The book "Modern C++ Design" covers this issue nicely.
Google Books contains scans of much of it - see section 6.5 (page 135) - link.
You can handle this cleanly, by putting pointers to the objects in global space, then newing them in the desired order in your main, and destroying them in the desired order at the end of the main.
As others pointed, there is no standard and portable way to solve this problem, due to the Static initialization order fiasco issue.
However, you should be able to solve your problem by applying a bit of design, so you gain a degree of control when (and how) objects of A and B are constructed. Take a look on design patterns like creational pattern Singleton it is considered in many (if not most) cases as anti-pattern, despite it is worth to learn about it. Also look at to Monostate pattern which may be used as a bit better Singleton. These patterns can help to control object creation and lifetime, so things are properly initialized before use.
Generally, it's a good idea to avoid globals - sticking to deglobalisation is a good idea.
In case if you use lazy singletons (that return on-demand created statics) you may end up with possibility where one singleton uses another one after it was already deleted. For example, imagine you have a global HttpClient
singleton that allows you to make http requests. Also, you probably want to have logging, which might be provided by Log
singleton:
class HttpClient
{
...
static HttpClient& singleton()
{
static HttpClient http;
return http;
}
};
Same for Log
singleton. Now, imagine HttpClient
constructor and destructor simply log that that HttpClient
was created and deleted. In this case destructor of HttpClient
may end up using deleted Log
singleton.
Sample code:
#include <stdio.h>
class Log
{
Log()
{
msg("Log");
}
~Log()
{
msg("~Log");
}
public:
static Log& singleton()
{
static Log log;
return log;
}
void msg(const char* str)
{
puts(str);
}
};
class HttpClient
{
HttpClient()
{
Log::singleton().msg("HttpClient");
}
~HttpClient()
{
Log::singleton().msg("~HttpClient");
}
public:
static HttpClient& singleton()
{
static HttpClient http;
return http;
}
void request()
{
Log::singleton().msg("HttpClient::request");
}
};
int main()
{
HttpClient::singleton().request();
}
and the output is:
Log
HttpClient
HttpClient::request
~HttpClient
~Log
So far everything is correct, simply because it happened that Log
was constructed before HttpClient
, which means that HttpClient
can still use Log
in its destructor. Now simply comment out logging code in HttpClient
constructor and you'll end up with this output:
Log
HttpClient::request
~Log
~HttpClient
As you can see, log is being used after its destructor ~Log
was already called. As pointed out, deglobalization might be a better approach, but if you want to use on-demand created singletons and make some of them live longer than the others, then you can make such singletons use a global static initialized on demand. I use that approach quite often in production code:
class Log
{
friend std::unique_ptr<Log>::deleter_type;
...
static std::unique_ptr<Log> log;
static Log& createSingleton()
{
assert(!log);
log.reset(new Log);
return *log;
}
public:
static Log& singleton()
{
static Log& log = createSingleton();
return log;
}
};
std::unique_ptr<Log> Log::log;
Now, regardless of order in which these singletons were constructed, destruction order will ensure that Log
is destructed after HttpClient
. This, however, might still fail and produce unexpected output if HttpClient
was used from a global static constructor. Or if you want to have multiple such "super" globals (like Log
and Config
for example) that use each other in random order, you will still have these problem. In such cases sometimes it's just better allocate once on heap never delete some of these objects.
精彩评论