std::atexit

  • Thread starter Christopher Pisz
  • Start date
C

Christopher Pisz

I am attempting to write a "Phoenix Singleton" using the book "Modern C++
Design" by Alexandrescu
I do not understand his use of...

#ifndef ATEXIT_FIXED
std::atexit(Kill);
#endif

....in the source below. I understand the problem, but not how a preprocessor
directive will fix it. He says the standard is unclear about the situation
where one call to register with std::atexit is the result of is made as an
effect of another std::atexit registration. Can anyone be more specific on
how to fix the problem?

Source (forgive the indenting problems as a result of editor
incompatibilities):


// .h file
#ifndef SINGLETON_PHOENIX
#define SINGLETON_PHOENIX
#include "BaseException.h"
namespace cpisz_common_lib
{
//----------------------------------------------------------------------------------------------------------------------
/**
* Singleton_Pheonix
*
* A singleton pattern that guarentees an instance will always be available.
*
*/
class Singleton_Phoenix
{
public:
/**
* Retreives a reference to this singleton
*/
static Singleton_Phoenix & GetInstance();
private:
static void Create();
static void Kill();
static void OnDeadReference();
Singleton_Phoenix();
Singleton_Phoenix(const Singleton_Phoenix &);
virtual ~Singleton_Phoenix();
Singleton_Phoenix & operator = (const Singleton_Phoenix &);
static Singleton_Phoenix * m_instance; // Pointer to the instance of this
object
static bool m_destroyed; // Flag that indicates the instance has been
destroyed
};
} // namespace cpisz_common_lib
#endif

// .cpp file
#include "Singleton_Phoenix.h"
#include <cstdlib>
namespace cpisz_common_lib
{
//----------------------------------------------------------------------------------------------------------------------
Singleton_Phoenix * Singleton_Phoenix::m_instance = 0;
bool Singleton_Phoenix::m_destroyed = false;
//----------------------------------------------------------------------------------------------------------------------
Singleton_Phoenix & Singleton_Phoenix::GetInstance()
{
// Check if the object is instantiated
if( !m_instance )
{
// Check for dead reference
if( m_destroyed )
{
// Create a new instance, replacing the old destroyed instance
OnDeadReference();
}
else
{
// Create the first instance
Create();
}
}
return *m_instance;
}
//----------------------------------------------------------------------------------------------------------------------
void Singleton_Phoenix::Create()
{
static Singleton_Phoenix instance;
m_instance = &instance;
}
//----------------------------------------------------------------------------------------------------------------------
void Singleton_Phoenix::Kill()
{
// Set the instance pointer to NULL and destroyed flag to true;
m_instance->~Singleton_Phoenix();
}
//----------------------------------------------------------------------------------------------------------------------
void Singleton_Phoenix::OnDeadReference()
{
// Get the shell of the old reference to the instance
Create();
// Create a new instance at that same address
// Using the "placement new operator"
new(m_instance) Singleton_Phoenix;
// Queue this new objects destruction
#ifndef ATEXIT_FIXED
std::atexit(Kill);
#endif
// Reset the destoryed flag
m_destroyed = false;
}
//----------------------------------------------------------------------------------------------------------------------
Singleton_Phoenix::~Singleton_Phoenix()
{
m_instance = 0;
m_destroyed = true;
}
} // namespace cpisz_common_lib
 
J

James Kanze

I am attempting to write a "Phoenix Singleton" using the book
"Modern C++ Design" by Alexandrescu I do not understand his
use of...
#ifndef ATEXIT_FIXED
std::atexit(Kill);
#endif
...in the source below. I understand the problem, but not how
a preprocessor directive will fix it. He says the standard is
unclear about the situation where one call to register with
std::atexit is the result of is made as an effect of another
std::atexit registration.

C99 is not unclear about this (although I seem to remember it
being undefined behavior in C90, and thus in C++98).
Can anyone be more specific on how to fix the problem?

Which problem? The code you posted has several different cases
of undefined behavior. All the #ifndef does is cause atexit not
to be called if ATEXIT_FIXED is defined.

I've not studied it in detail, but even after a quick glance, it
is apparent that if Kill() is ever called (and it will be called
if atexit is called), the destructor is called twice for the
same object. This is undefined behavior, and in a non-trivial
class, will almost certainly get you into trouble. So you
almost certainly have to defined ATEXIT_FIXED for the code to
work.

The obvious way to achieve the supposed goal (that an instance
will always be available) is to create the instance with new,
and never destruct it.
 
C

Christopher

[snip]
He says the standard is
unclear about the situation where one call to register with
std::atexit is the result of is made as an effect of another
std::atexit registration.

C99 is not unclear about this (although I seem to remember it
being undefined behavior in C90, and thus in C++98).

What does C99 say about it?


Which problem? The code you posted has several different cases
of undefined behavior. All the #ifndef does is cause atexit not
to be called if ATEXIT_FIXED is defined.

That is what I suspected. If that is the case I don't see any reason
to put the preprocessor directive in there at all. I should always
execute that block.

I've not studied it in detail, but even after a quick glance, it
is apparent that if Kill() is ever called (and it will be called
if atexit is called), the destructor is called twice for the
same object. This is undefined behavior, and in a non-trivial
class, will almost certainly get you into trouble. So you
almost certainly have to defined ATEXIT_FIXED for the code to
work.

So, the code will not have undefined behavior if I remove the
preprocessor directive in that case? I do not see the other cases you
speak of even after examining the code in detail. The only problem I
can forsee is what happens at application exit when a registration
using std::atexit() is made as a result of another registration using
atexit(), which is the behavior my post is asking about. Anyone see
other cases of undefined behavior? Anyone have a definition of the
behavior mentioned?

The obvious way to achieve the supposed goal (that an instance
will always be available) is to create the instance with new,
and never destruct it.

I am unclear about this suggestion. If I new something and do not
delete it, is that not a memory and possibly a resource leak? What
will happen in the case that one of these singletons is dependent on
another at the time of application exit?

I am getting the feeling that what you are telling me is that this
book is out of date and there may be a better solution available. If
so, does anyone have a link? I have googled and found several
singleton implementations, but none of which seem to address the flaws
that this one is attempting to address.

I really want to try to avoid the authors proposal of implementing a
"dependency manager" and global "setlongevity(int)" methods. It seems
overly complex to me.
 
J

James Kanze

On Jan 2, 6:19 am, "Christopher Pisz" <[email protected]>
wrote:
[snip]
C99 is not unclear about this (although I seem to remember it
being undefined behavior in C90, and thus in C++98).
What does C99 say about it?

That the registered functions will be called in the reverse
order of their registration, except when the registration occurs
after some of the functions have been called, those functions
will not be recalled. (The exact wording in the standard is far
more indirect, but that's about what it comes out to.)
That is what I suspected. If that is the case I don't see any reason
to put the preprocessor directive in there at all. I should always
execute that block.

I don't think so. I rather think that you should never execute
that block; executing it results in undefined behavior.
So, the code will not have undefined behavior if I remove the
preprocessor directive in that case?

I didn't study the code in detail, so I'm not sure. It does
seem like a complicated solution for a very simple problem.
I do not see the other cases you speak of even after examining
the code in detail. The only problem I can forsee is what
happens at application exit when a registration using
std::atexit() is made as a result of another registration
using atexit(), which is the behavior my post is asking about.
Anyone see other cases of undefined behavior? Anyone have a
definition of the behavior mentioned?

I'd have to study the code in detail to be sure, but I did
notice an explicit call of the destructor in there. On a
variable with static lifetime.

On relooking at it, I'm less sure---I think that Andrei is
trying to reuse the memory of the variable, and create a new
instance, if you call instance() after the destructor has run.
And that this is what he destructs in the registered Kill
function. I'm still sceptical---it looks like a lot of extra
complexity for nothing, and it still fails in some critical
cases:

-- If you need a destructor for the singleton, presumably, that
destructor is doing something important, that should only be
done once on process shutdown. Creating a new instance
during process shutdown doesn't seem to meet the
requirements of a singleton.

Most of the time, of course, you don't need to call the
destructor of a singleton. (I can't think of a case where
I've ever needed to.) So you just create it with new, once,
before threading starts, and be done with it. No delete, no
destructor called.

-- It's not rare for objects to only call instance() once on a
singleton, and keep the reference which was returned. I
expect that most cases where a singleton is used during
static destruction will be of this sort, so you'd really
need for instance to return some sort of smart pointer,
which would ensure that the destructor couldn't be called.
I am unclear about this suggestion. If I new something and do
not delete it, is that not a memory and possibly a resource
leak?

There's no memory leak on any system I've used. (The standard,
of course, doesn't say anything at all about what happens after
your program ceases to run. From the standard point of view,
once the program ceases to run, the universe with which the
standard is concerned ceases to exist.)

It could potentially result in a resource leak, e.g. a temporary
file that doesn't get deleted. But that is a problem which you
must resolve otherwise anyway: on most systems, there are ways
of stopping a process without going through the normal shutdown
process, and those can't be allowed to leak resources either.
(Think of it for a moment. If someone does a kill -9 on your
process, it still shouldn't leak resources.)
What will happen in the case that one of these singletons is
dependent on another at the time of application exit?

I'm not sure what your problem is. I never destruct my
singletons, and it works fine. I also keep destructors simple:
a good destructor doesn't depend on any additional resources,
and if a destructor must, then I document that it cannot be used
for a static object, and that all instances must be destructed
before program shutdown. (I can't think of any such class off
hand, however.)
I am getting the feeling that what you are telling me is that
this book is out of date and there may be a better solution
available.

Out of date, certainly not. I rather suspect that for many
things, it still isn't in date---it pushed compilers pretty far
when it appeared, and I'm not sure that the situation has
improved enough since then that you can reliably count on the
techniques working without hacky compiler work-arounds.

I do think that Andrei tends to find complicated solutions for
problems which don't really exist in practice. I would tend to
view the book more as a display of just what you can do with
C++, if you really need to, but I would not jump into overly
generic (and complicated) solutions unless there was a real
need. I'm a firm believer in the KISS principle (except when
I'm having fun).
If so, does anyone have a link? I have googled and found
several singleton implementations, but none of which seem to
address the flaws that this one is attempting to address.

Maybe because most people don't consider them to be *flaws*. Or
at the most, theoretical, and not practical flaws. I would
consider this implementation to have a "flaw" as well, since
under certain circumstances, the singleton object gets
constructed and then destructed several times.
I really want to try to avoid the authors proposal of
implementing a "dependency manager" and global
"setlongevity(int)" methods. It seems overly complex to me.

My own singleton template isn't yet available at my site, but
it's simple enough that I can post it here:

enum DestructionPolicy
{
neverDestruct,
destructOnExit
} ;

template< typename UserClass, DestructionPolicy dtorPolicy =
neverDestruct >
class Singleton
{
public:
static UserClass& instance() ;

private:
static UserClass* ourInstance ;

template< DestructionPolicy discrimPolicy >
class Discrim {} ;
static UserClass* createInstance( Discrim< neverDestruct
static UserClass* createInstance( Discrim< destructOnExit
} ;

template< typename UserClass, DestructionPolicy dtorPolicy >
UserClass* Singleton< UserClass, dtorPolicy >::
ourInstance
= &Singleton< UserClass, dtorPolicy >::instance() ;

template< typename UserClass, DestructionPolicy dtorPolicy >
UserClass&
Singleton< UserClass, dtorPolicy >::instance()
{
if ( ourInstance == NULL ) {
ourInstance = createInstance( Discrim< dtorPolicy >() ) ;
}
return *ourInstance ;
}

template< typename UserClass, DestructionPolicy dtorPolicy >
UserClass*
Singleton< UserClass, dtorPolicy >::createInstance(
Discrim< neverDestruct > )
{
return new UserClass ;
}

template< typename UserClass, DestructionPolicy dtorPolicy >
UserClass*
Singleton< UserClass, dtorPolicy >::createInstance(
Discrim< destructOnExit > )
{
static UserClass theOneAndOnly ;
return &theOneAndOnly ;
}

It uses a simple policy to decide between never destruct, and
destruct during the destruction of static variables; if you
select the second, of course, you can run into order of
destruction problems if you're stupid enough to use it in the
destructor of a static object. (In practice, I don't think I've
ever used the destructOnExit policy in a real application,
however. And I don't think I've ever used the singleton in the
destructor of an object with static lifetime.) This
implementation also manages to be thread safe without a lock;
the code you posted is *not* thread safe, and cannot be used in
a multithreaded envirionment without adding mutexes.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,744
Messages
2,569,484
Members
44,903
Latest member
orderPeak8CBDGummies

Latest Threads

Top