std::atexit

Discussion in 'C++' started by Christopher Pisz, Jan 2, 2008.

  1. 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
     
    Christopher Pisz, Jan 2, 2008
    #1
    1. Advertising

  2. Christopher Pisz

    James Kanze Guest

    On Jan 2, 6:19 am, "Christopher Pisz" <> wrote:
    > 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.

    --
    James Kanze (GABI Software) email:
    Conseils en informatique orientée objet/
    Beratung in objektorientierter Datenverarbeitung
    9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34
     
    James Kanze, Jan 2, 2008
    #2
    1. Advertising

  3. Christopher Pisz

    Christopher Guest

    On Jan 2, 6:02 am, James Kanze <> wrote:
    > On Jan 2, 6:19 am, "Christopher Pisz" <> wrote:
    >

    [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?



    > > 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.


    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.
     
    Christopher, Jan 2, 2008
    #3
  4. Christopher Pisz

    James Kanze Guest

    On Jan 2, 6:32 pm, Christopher <> wrote:
    > On Jan 2, 6:02 am, James Kanze <> wrote:>
    > On Jan 2, 6:19 am, "Christopher Pisz" <>
    > wrote:
    > [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?


    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.)

    > > > 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.


    > 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.

    > > 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 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.

    > > 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?


    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.

    --
    James Kanze (GABI Software) email:
    Conseils en informatique orientée objet/
    Beratung in objektorientierter Datenverarbeitung
    9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34
     
    James Kanze, Jan 2, 2008
    #4
    1. Advertising

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

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.

Share This Page