| Home | Forums | Reviews | Guides | Newsgroups | Register | Search |
![]() |
| Thread Tools |
| Christopher Pisz |
|
|
|
| |
|
James Kanze
Guest
Posts: n/a
|
On Jan 2, 6:19 am, "Christopher Pisz" <some...@somewhere.net> 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++9 > 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 |
|
|
|
| |
|
Christopher
Guest
Posts: n/a
|
On Jan 2, 6:02 am, James Kanze <james.ka...@gmail.com> wrote:
> On Jan 2, 6:19 am, "Christopher Pisz" <some...@somewhere.net> 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++9 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 |
|
James Kanze
Guest
Posts: n/a
|
On Jan 2, 6:32 pm, Christopher <cp...@austin.rr.com> wrote:
> On Jan 2, 6:02 am, James Kanze <james.ka...@gmail.com> wrote:> > On Jan 2, 6:19 am, "Christopher Pisz" <some...@somewhere.net> > 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++9 > 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 |
|
|
|
| |
![]() |
| Thread Tools | |
|
|
Powered by vBulletin®. Copyright ©2000 - 2013, vBulletin Solutions, Inc..
SEO by vBSEO ©2010, Crawlability, Inc. |



