Explicitly calling a destructor in a signal handler
I have a destructor that performs some necessary cleanup (it 开发者_如何学编程kills processes). It needs to run even when SIGINT is sent to the program. My code currently looks like:
typedef boost::shared_ptr<PidManager> PidManagerPtr
void PidManager::handler(int sig)
{
std::cout << "Caught SIGINT\n";
instance_.~PidManagerPtr(); //PidManager is a singleton
exit(1);
}
//handler registered in the PidManager constructor
This works, but there seem to be numerous warnings against explicitly calling a destructor. Is this the right thing to do in this situation, or is there a "more correct" way to do it?
If that object is a singleton, you don't need to use a shared-pointer. (There's only one!)
If you switch it to auto_ptr
you can call release()
on it. Or perhaps scoped_ptr
, calling reset()
.
This all said, I'm 99% certain that exit()
will destruct statically constructed objects. (Which singletons tend to be.) What I do know is that exit()
calls the registered atexit()
functions.
If your singleton is not destructed automatically by exit, the proper thing to do in your case is to make an atexit
hook:
void release_singleton(void)
{
//instance_.release();
instance_.reset();
}
// in main, probably
atexit(release_singleton);
Never explicitly call destructor unless object was constructed with placement new. Move cleanup code into separate function and call it instead. The same function is to be called from the destructor.
Turns out that doing this was a very bad idea. The amount of weird stuff going on is tremendous.
What was happening
The shared_ptr had a use_count of two going into the handler. One reference was in PidManager itself, the other was in the client of PidManager. Calling the destructor of the shared_ptr (~PidManager() ) reduced the use_count by one. Then, as GMan hinted at, when exit() was called, the destructor for the statically initialized PidManagerPtr instance_ was called, reducing the use_count to 0 and causing the PidManager destructor to be called. Obviously, if PidManager had more than one client, the use_count would not have dropped to 0, and this wouldn't have worked at all.
This also gives some hints as to why calling instance_.reset() didn't work. The call does indeed reduce the reference count by 1. But the remaining reference is the shared_ptr in the client of PidManager. That shared_ptr is an automatic variable, so its destructor is not called at exit(). The instance_ destructor is called, but since it was reset(), it no longer points to the PidManager instance.
The Solution
I completely abandoned the use of shared_ptrs and decided to go with the Meyers Singleton instead. Now my code looks like this:
void handler(int sig)
{
exit(1);
}
typedef PidManager * PidManagerPtr
PidManagerPtr PidManager::instance()
{
static PidManager instance_;
static bool handler_registered = false;
if(!handler_registered)
{
signal(SIGINT,handler);
handler_registered = true;
}
return &instance_;
}
Explicitly calling exit allows the destructor of the statically initialized PidManager instance_ to run, so no other clean up code need be placed in the handler. This neatly avoids any issues with the handler being called while PidManager is in an inconsistent state.
You really don't want to do much of anything in a signal handler. The safest the thing to do is just set a flag (e.g. a global volatile bool), and then have your program's regular event loop check that flag every so often, and if it has become true, call the cleanup/shutdown routine from there.
Because the signal handler runs asynchronously with the rest of the application, doing much more than that from inside the signal handler is unsafe -- whatever data you might want to interact with might be in an inconsistent state. (and you're not allowed to use mutexes or other synchronization from a signal handler, either -- signals are pretty evil that way)
However, if you don't like the idea of having to poll a boolean all the time, one other thing you can do from within a signal handler (at least on most OS's) is send a byte on a socket. So you could set up a socketpair() in advance, and have your normal event loop select() (or whatever) on the other end of the socket pair; when it receives a byte on that socket, it knows your signal handler must have sent that byte, and therefore it's time to clean up.
One other way could be to have the singleton dynamically allocated (on first use or in main), and delete
it for cleanup.
Eh. I guess your PidManagerPtr actually points to a dynamically allocated object ... But doesn't boost::shared_ptr actually clean up on reallocation? So it should be enough to:
instance_ = 0;
?
Just call reset() on the shared_ptr and it'll remove your instance for you.
精彩评论