
This message is an in depth description of my current design plan for a new singleton library, which I hope to complete and schedule for review by boost in the coming year. I would appreciate any comments, questions, or suggestions regarding this plan before I begin work on the code. Specifically, I would like to know if there are any important issues that this design fails to address, or difficulties with the interface that would turn away potential users. (My old singleton library, which was not accepted by boost, is available at http://tinyurl.com/6qvrd. It was rejected primarily because it failed to adequately address concerns related to multi threading and dlls.) Thus far, my high level design plan looks like this: The creator_policy defines fundamental pointer, const_pointer, and reference types. These hold references to a creator policy instance which maintains the singleton internally, so that the pointer types can always see and access the state of the single instance. This way, pointers to the singleton instance are not invalidated when the state of the singleton changes. The creator_policy defines create, replace, and destroy methods, as well as methods to generate instances of the various pointer types. Create only creates the instance if it does not already exist, destroy only destroys the instance if it does exist, and replace makes sure any existing instance is destroyed before attempting creation. Thus far, I hope to support the following creators: -------------------------------------------------------------------------------- in_place_creator creates/destroys inside an aligned_storage POD interface: create ( [ctor params] ); replace ( [ctor params] ); destroy ( ); pros: no memory allocation/deallocation ever required constructor params can be easily specified cons: automatic creation, when it occurs, always uses the default constructor singleton cannot be created as a derived type useful for simple, user defined singleton classes -------------------------------------------------------------------------------- source_sink_creator owns fixed source and sink functors, specified with template parameters interface: create ( ); replace ( ); destroy ( ); pros: source can return a derived type automatic creation uses source rather than default construction cons: source and sink cannot be set or changed at runtime constructor arguments cannot be specified by calling code useful for singleton types provided by untouchable code, which may require calling special functions to get pointers to instances -------------------------------------------------------------------------------- dynamic_source_sink_creator owns dynamic source and sink functors interface: create ( [source[, sink]] ); replace ( [source[, sink]] ); destroy ( [source[, sink]] ); pros: source can return a derived type automatic creation uses source rather than default construction source and sink can optionally be changed at run time source and sink can change functor types when they are set (example: from function pointer to lambda expression) cons: specifying constructor parameters to pass is non-trivial dynamic memory allocation and virtual functions must be used to store the functors in order to allow for different functor types at runtime, thus creation is more expensive and can potentially fail useful for a singleton which needs to take the forms of unspecified derived types and is frequently destroyed and created via some type of factory -------------------------------------------------------------------------------- The lifetime_policy owns and wraps the creator policy, and provides pointer and reference types which wrap those belonging to the creator. It also provides functions to generate instances of the various pointer types, and provides a function to access the creator_policy instance. The unmanaged lifetime simply typedefs the underlying pointer and reference types, and thus results in no overhead. The longevity_lifetime and lifo_lifetime behave similarly, but handle scheduling of automated destruction when any of the pointer generation methods are called. The dependency_lifetime will wrap the internal pointer types to perform reference counting (the reference count itself will be stored in the lifetime_policy instance). The timeout_lifetime will manage a separate thread that releases the instance when it finishes counting down. It will also wrap the internal pointer types to reset the timer when a pointer is dereferenced, and reset and lock the timer while any references exist. The threading_policy owns and serializes access to the lifetime_policy. It also has a function to get access to the lifetime_policy instance, and provides pointer, const_pointer, and reference types which wrap and forward to the lifetime's corresponding types. For the single_threaded policy, these pointer types are simply typedefs of the lifetime's pointer types, resulting in no overhead, and the function through which the lifetime_policy instance is accessed just returns a raw pointer to it. For the multi_threaded policy, pointer types perform locking before forwarding to lifetime pointer types, and the reference type acts as a lock throughout it's existence. The mutex is stored inside the threading_policy instance. The function which returns a pointer to the lifetime_policy instance returns a smart pointer which serializes access. A storage_policy determines how instances of the threading_policy are stored (and because these instances contain instances of the other policies, this indirectly controls how all of the policy instances are stored). static_storage stores policy instances directly as global variables, which is fine for singletons that are not required to have a specific lifetime with regard to other singletons. These global variables will be set up in such a way that it will be guaranteed that any automatic destruction takes place before the policy bundle is destroyed. group_storage allows multiple singletons to have their policies stored in a master group. No policies in this group are destroyed until all automated destruction that will be performed by any members of the group has completed. This way, if destructors of some of the singletons in this group trigger re-creation of other singletons in the same group, the policies needed to recreate the singletons are guaranteed to still exist. dll_storage requires linking with singleton.dll, and stores instances in a special exported manager singleton to ensure unique singleton instances across dll boundaries. All policy instances of this type are guaranteed to be destroyed only after all automated destruction has taken place. A singleton class exists, which takes a singleton type and a name tag as template parameters. The name will have a default type called 'unnamed'. This name parameter enables using multiple singleton instances of the same type. Policies are traits of the name specified, so using the default name 'unnamed' results in using the default policies. Policies can be specified by declaring a name as an undefined struct, and then specializing the desired policy traits for that name. The singleton class contains three pointer types, which wrap those of the threading policy. These are strong_pointer, lazy_pointer, and weak_pointer. strong_pointer attempts automatic creation of the singleton instance if it does not exist when the pointer is first created, and also attempts automatic creation if needed any time it is dereferenced. lazy_pointer only attempts automatic creation when dereferenced, and weak_pointer will never create the instance automatically. For the lifo_lifetime, a strong_pointer can be used to specify a dependency of some singleton B on some singleton A by having B own a strong_pointer to A. This will ensure that A is created before B and destroyed after B. Const versions of these three pointer types will also exist. Creation, destruction, and non-const member functions will not be allowed to be called through these const pointer types. A single reference type will exist which will be the result of dereferencing any pointer type via the unary * operator. const_pointers return const qualified references. Pointers only perform temporary locking (if the threading policy supports locking) when a member function is called via the -> operator or when a create or destroy operation is invoked. References lock the singleton throughout their entire lifetime, and thus can be used to make a series of operations on the singleton instance atomic. A reference provides an implicit conversion to a raw reference of the singleton's type (which comes back const if the reference is const). Create and destroy can only be called on non-const references. Typical code will access a singleton instance by creating one of the above pointer types and calling member functions through it via the -> operator. Ex: singleton < MyType, SomeName >::lazy_pointer ptr; ptr->DoSomething ( ); -------------------------------------------------------------------------------- I plan to sneak an improved version of the null developed by Scott Meyers in with this code in a header called boost/null.hpp, and use it in the singleton's implementation. It will be defined as follows: namespace boost { const class { private: void operator & ( ) const; public: #ifndef BOOST_NO_MEMBER_TEMPLATES // provide conversion to any pointer type template < typename Class, typename Type > operator Type Class::* ( ) const { return 0; } template < typename Type > operator Type * ( ) const { return 0; } #else //provide conversion to int if member templates are not supported operator int ( ) const { return 0; } #endif//BOOST_NO_MEMBER_TEMPLATES } null; } #undef NULL #define NULL (::boost::null) -------------------------------------------------------------------------------- Any and all comments or suggestions on this plan are welcomed. Potential problems are much easier to address at design time than after the code is written. -Jason

Michael Goldshteyn wrote:
const class { ... } null; Defines a global variable named null, which is constant and of an unnamed type defined by the class declaration. I think this is standards conforming, but now that you mention it I'm not positive. I will have to check with other compilers to make sure. -Jason

From: "Michael Goldshteyn" <mgoldshteyn@comcast.net>
Incomplete. Seriously, here's the whole thing:
That's roughly the same as this: namespace boost { class x { ... }; x null; } The difference is that the class is anonymous, so you can't create any other instances thereof. -- Rob Stewart stewart@sig.com Software Engineer http://www.sig.com Susquehanna International Group, LLP using std::disclaimer;

Michael Goldshteyn wrote:
It worked with VC, but I just checked it with comeau and it failed to compile. To fix it, I had to provide a name for the class and an explicit default constructor. Naming the class null works, because then the class name is hidden by the global variable name and thus other instances of type null cannot be declared. Updated code: namespace boost { const class null { private: void operator & ( ) const; public: null ( ) { } #ifndef BOOST_NO_MEMBER_TEMPLATES // provide conversion to any pointer type template < typename Class, typename Type > operator Type Class::* ( ) const { return 0; } template < typename Type > operator Type * ( ) const { return 0; } #else //provide conversion to int if member templates are not supported operator int ( ) const { return 0; } #endif//BOOST_NO_MEMBER_TEMPLATES } null; } #undef NULL #define NULL (::boost::null) -Jason

From: Jason Hise <chaos@ezequal.com>
That may cause grief to those using the -Wshadow option of recent vintage GCC's.
According to the grammar, Comeau is wrong: class-specifier: class-head { member-specification(opt) } class-head: class-key identifier(opt) base-clause(opt) class-key nested-name-specifier identifier base-clause(opt) class-key nested-name-specifier(opt) template-id base-clause(opt) class-key: class struct union Note that "identifier" is optional. -- Rob Stewart stewart@sig.com Software Engineer http://www.sig.com Susquehanna International Group, LLP using std::disclaimer;

Rob Stewart wrote:
According to the grammar, Comeau is wrong:
[...]
I think comeau just complained about not having a default constructor explicitly defined, and in order to define a constructor I had to give the class a name. I'm not sure why it wanted the default constructor to be explicitly defined, though.
That may cause grief to those using the -Wshadow option of recent vintage GCC's.
Perhaps I can just provide the name for comeau compilations then. -Jason

In article <dcbg9e$eap$1@sea.gmane.org>, "Michael Goldshteyn" <mgoldshteyn@comcast.net> wrote:
A declaration of a const variable (named null -- read to the end of his code) which type is an anonymous class declared as part of the variable declaration. Ben -- I changed my name: <http://periodic-kingdom.org/People/NameChange.php>

Michael Goldshteyn wrote:
Please, reduce the amount of text you quote. const class { } null // is an attempt to create a constant object of an unnamed class type. Regards, m Send instant messages to your online friends http://au.messenger.yahoo.com

Jason Hise wrote:
[...]
#undef NULL #define NULL (::boost::null)
I violently object to sneaking in something that fundamental. I also object to this idea in general. It doesn't buy us much, it breaks existing code, it may cause ODR violations, and it also violates the C++ Standard. Regards, m Send instant messages to your online friends http://au.messenger.yahoo.com

Martin Wille wrote:
If I was really trying to 'sneak' it in I wouldn't have mentioned it explicitly on the list. ;)
What existing code will it break? I would imagine it would only break code that deserves to be broken in the first place (like int i = NULL). Also, how would this cause ODR violations? I was under the impression that the one definition rule required all definitions in different translation units to be identical, not that it required that a definition only be provided in one translation unit. And even if somehow more than one instance of null ended up being used, how would this actually end up causing a problem? Would your problem with this code vanish if I removed the NULL definition on the last two lines? Would it reappear if I moved the null class into the global namespace (essentially making lowercase null a keyword)? Please don't take my response as confrontational, I do appreciate the feedback. -Jason

Eric Niebler wrote:
Bad example. This is OK, somewhat surprisingly (to me, at least). But consider: struct foo { foo(void*) {} }; void bar(foo f) {} void baz() { bar(NULL); } Works with NULL as 0, fails with NULL as null because it needs two user-defined conversions.
Redefining NULL is a career-limiting move. Don't do it.
This is still correct, though. -- Eric Niebler Boost Consulting www.boost-consulting.com

Eric Niebler wrote:
Alright, I concede that I shouldn't redefine a standard macro. :-P Interesting example by the way... hadn't thought of that. Would be nice if there was a way around it. I find the upper limit on user defined implicit conversions frustrating. This limit has already thwarted my plan to make a smart reference type which only locks the contained instance while being implicitly converted to the raw reference type. It would have worked something like: template < typename Type > struct Ref { struct InternalRef { Lock my_lock; operator Type & ( ); }; operator InternalRef ( ); }; This was planned to work similar to the locking pointer idiom: template < typename Type > struct Ptr { struct InternalPtr { Lock my_lock; Type * operator -> ( ); }; InternalPtr * operator -> ( ); }; But sadly, the locking reference idea requires two implicit user defined conversions. And I can't exactly overload the '.' operator. *sigh* Why does the C++ language have to ban such potentially fun things as infinite implicit conversion chains and overloading the dot operator? -Jason

"Jason Hise" <chaos@ezequal.com> skrev i meddelandet news:42E9A2B1.4020407@ezequal.com...
Why does the C++ language have to ban such potentially fun things as infinite implicit conversion chains and overloading the dot operator?
Because it would force the compiler to create conversion chains thru any unrelated types, that just happen to be present. Combine that with overload resolution, C style type promotions, and templates, and you have got what? A real mess! struct x { operator int(); }; struct y { }; Here, an x isn't convertible to a y. Good! struct z { z(int); operator y(); }; Now they would be! Bo Persson

Jason Hise wrote:
Eric already provided an example for code breaking with your definition of NULL. There's nothing wrong with int i = NULL. It's certainly not good style, but that doesn't make it deserve to be broken.
a.hpp: #include "boost/null.hpp" b.hpp: #include <cstddef> c.hpp: /* e.g. template code that uses NULL */ x.cpp: #include "b.hpp" #include "a.hpp" #include "c.hpp" /* use stuff from c.hpp */ y.hpp: #include "b.hpp" #include "c.hpp" /* use stuff from c.hpp */ x and y will instantiate different code due to the different #include order. And even if
somehow more than one instance of null ended up being used, how would this actually end up causing a problem?
It's undefined behaviour. Would your problem with this
code vanish if I removed the NULL definition on the last two lines?
Yes.
Would it reappear if I moved the null class into the global namespace (essentially making lowercase null a keyword)?
Adding stuff to the global namespace bears its own set of problems. You would definitely break a lot of code that defines its own null constants. It's certainly less of a problem than re#defining NULL would be. Regards, m Send instant messages to your online friends http://au.messenger.yahoo.com

Even though you did not bother to comment on my formal review of first submission (http://article.gmane.org/gmane.comp.lib.boost.devel/124201) all the points there are still valid (IMO). And the most important is: should we do this at all? IMO it's bad idea to promote smart singletons in single threaded environment (beyond what we could implement using trivial means like "Meyers singleton") and it twice as bad in MT where there is no relyable way to implement efficient access. Gennadiy

Gennadiy Rozental wrote:
I'm sorry that I did not remember to respond to your review at the time... I was swamped with finals at the time, but that's not an excuse. Let me try to address your concerns now. A) class destructors preferably shouldn't be doing anything smart I don't understand the reasoning behind this... class destructors exist so that resources can be disposed of intelligently. If this were valid then we wouldn't have reference counted smart pointers. B) if teardown procedure is not trivial it's part of normal program flow and should reside within main() bounds This forces the highest level code to be aware of every detail that needs to be cleaned up. What if a library wants to use some sort of resource manager in the background? Should it force client code to be aware that said resource manager exists, and make client code responsible for cleaning it up? This seems like a C-oriented way of thinking, and does not scale. C) it doesn't seem to be ever portable to rely on order of global variables destruction in different libraries (dynamic or static) The portable means to control this is with mini meyers singletons. It is guaranteed by the standard that local static variables will be destroyed in reverse order of the completion of their constructors. Thus with the following code: Foo & A ( ) { static Foo foo; return foo; } Bar & B ( ) { A ( ); static Bar bar; return bar; } If B is called, then B's bar will definitely be destroyed after A's foo. D) it's unreasonable to expect all global variables to employ single manager facility within bound any reasonably big application Hence the policy based design. Related singletons can use related lifetime managers, and unrelated singletons need not be concerned about each other. The dependency_lifetime solution handles this concern especially well, as using reference counted smart pointers to singleton instances does not require a master manager at all. E) if any teardown procedure fails it's really non-trivial to insure proper destruction for the rest of globals (if possible at all), while reporting an error may be impossible/difficult. This is not a problem with singletons, but a more general problem which client code must be concerned about. Singletons which have a well defined order of destruction can help client code to organize a well structured solution. Many of your other points do not still apply, as there does exist a scalable mechanism to control destruction order, and the new design does take multi-threading into consideration. Just because it is relatively inefficient to access a singleton shared between threads safely does not mean that it is a bad approach. Any method employed to communicate between threads will be somewhat expensive. The goal is to minimize the amount of communication required, not to throw out communication mechanisms all together. With regard to your desire for a different interface, it is easy enough to create an instance method which wraps the smart pointer and just returns a raw reference. However, choosing to do so does eliminate all thread safety, destroys the possibility of delaying creation until a member function is accessed, and ensures that your references will not stay up to date if the singleton instance is destroyed. I will concede that including all policies from the same header in the old design was a bad idea. It was expensive and unnecessary. The new design will require including such headers individually, and when all of the simplest policies are used should not be much more expensive to use than a meyers singleton (assuming the compiler optimizes away functions that just forward to other functions). It feels like much of your argument is against object oriented principles in general, and advocates a single flow of execution. While this may be reliable for smaller programs, I have no idea how you can expect that solution to scale. -Jason

Disposing of resource this instance own is one (simple) thing. Meddle with other templates is something completely different (complex) and in most cases would involve function invocations which in turn may throw an exceptions and all the hell break loose. On the other hand if destructors are simple they are independent and all this complex machinery in unnecessary.
Client code should be aware about single function teardown() that user should call somewhere during program exit. After that any attempts to access library functionality is invalid. If you want you could even automate it by RADII based manager that does lib_init() in constructor and lib_teardown() in destructor.
This seems like a C-oriented way of thinking, and does not scale.
Both statements seems to be born out of thin air. Could you elaborate?
That was my point that Meyers singletons is all that we need. Anything smarter than that (IOW all these games with enforcing unnatural order of destruction) is just unnecessary "smart".
This still assumes that all world will be using your framework to implement singletons. Let say you need to use third party singleton in your destructor that is implemented by some arrogant somebody without regards to your order management?
Singletons with nontrivial logic in destructors ... should not do this in destructor. And this is not a general problem. It's problem with all global variables that destructed in afterlife (most main() exit)
Even if it exist (which you did not prove AFAICT) don't go there. "Don't go in language dark corners" experts told recently.
Just because it is relatively inefficient it makes it completely unusable. MT in many cases are very performance aware and singleton is a performance bottleneck.
Not every method. Just init you singleton in main thread and destroy in main thread. And no need to care about synchronization at all.
No need to synch on instance access at all.
and ensures that your references will not stay up to date if the singleton instance is destroyed.
Don't keep references and/or don't destroy your global variables in a middle of program execution.
It feels like much of your argument is against object oriented principles in general, and advocates a single flow of execution.
Let's not make unfounded accusations. Where did you see me argue against OOD?
And I have know idea what you mean. It would be good to base your statements with at least examples. Regards, Gennadiy

Gennadiy Rozental wrote:
None of the automated cleanup functionality in my original library did anything that could result in an exception being thrown. The dependency_lifetime used simple reference counting to release the instance, and the static_lifetime just used the property of order of initialization of static variables to guarantee an order of destruction. Neither of these was complex and/or coupling to other singletons. I admit that the longevity_lifetime and lifo_lifetime did use a rather expensive internal registry, but quite honestly I don't think that these lifetimes were as useful as the other two and they were merely provided for completeness. Regardless, their automated cleanup mechanisms could not throw either.
My singleton library gave the client code that option. Client code could explicitly destroy the instance early rather than relying on it being destroyed automatically. There was even a lifetime policy available which did not do any automated cleanup whatsoever, and a cleanup class designed specifically to lock singleton lifetimes to specific scopes (like the scope of main). I don't see why the client code should be forced to clean up global resources manually when reliable automated methods exist. I think it should be allowed to manage such things manually if it wants to, but the choice for automated resource management should be available.
This seems like a C-oriented way of thinking, and does not scale.
Both statements seems to be born out of thin air. Could you elaborate?
I was referring to managing all global resources and their dependencies at the bottom of main, or in cleanup functions that had to be called by main, rather than in destructors. This seems like a poor organizational method to use for large projects, especially if such projects have many shared resources.
I don't understand how my automated methods of destruction are unnatural. When you can express dependencies between singletons directly and cleanly, rather than having to hard code lifetime relationships into the bottom of main, it seems more natural to me.
Perhaps you were not aware when you wrote this that my library does offer the option (regardless of the automated cleanup policy used) to destroy any singleton manually at any time. As such, it is perfectly possible to set up lifetime relationships between singletons from my framework, singletons from other libraries, and even global variables.
If code should not perform non-trivial cleanup in destructors, then when should it do it? Regardless of where the logic is put, if cleanup fails there isn't a whole lot you can do. You pretty much would just have to swallow exceptions or terminate the program anyhow, and with destructors you are at least guaranteed that cleanup is attempted.
It isn't a dark corner, it is a well defined part of the standard. I am talking about using reference counting and the guaranteed order of destruction of meyers singletons to build a scalable mechanism of managing dependencies between global resources. The interface that the library provides makes managing the order of destruction as simple as having one singleton own a pointer to another, which is about as clean and decoupled a solution as I can imagine. class A { }; class B { A::pointer; // ensures that A outlives B };
So what would you advise as a mechanism to communicate global information between threads efficiently?
This assumes that the state of the singleton is read-only for its entire lifetime. If the singleton has a state which can be modified, how can you avoid caring about synchronization? If the singleton is used to perform actual runtime communication between threads, its state cannot be read-only.
See above.
I am talking about delaying creation until a member function is called, not until instance is called. I suppose this difference may be trivial, and is not the main point of my argument.
That places unnecessary restrictions on client code. Code should be given many available options, not just one fixed way of thinking about and implementing things.
In promoting the idea that all global resource management be handled at the bottom of main, rather than expressing dependencies between global resources in a more abstract and scalable way. Also in advocating against the use of destructors to perform cleanup.
I hope that I have clarified this above. -Jason

Whatever your library doing is beside the point here. My position is that *user* singletons shouldn't do anything smart in destructors that may cause a exception to be thrown. Therefore there should not be any dependencies in between singletons during destruction. Therefore order of destruction shouldn't matter and whatever you are doing is unnecessary.
You library has nothing to do with this option. Nobody could prevent me from implementing teardown() method and RAII based manager.
I do not see how it related.
I don't see why the client code should be forced to clean up global resources manually
It should not necessarily need to be manual. Just explicit.
In majority of the cases singleton cleanup shouldn't do anything smart but resource release. So the wouldn't be any intersingleton dependencies and order of destruction is not important. And in large projects that are implemented by combined efforts and or using third party libs you shouldn't expect everybody follow you organization. Even single "bad sheep" will cause all the hell break loose.
C) it doesn't seem to be ever portable to rely on order of global variables destruction in different libraries (dynamic or static)
[...]
Do not garble my words. I mean "unnatural order of destruction". There is one *automatically* selected/enforced by compiler order of global variables destruction. Do not mess with it.
The most natural and safe strategy IMO is *do not think about dependencies between singletons* in destructors. To express a specific order of singletons cleanup function invocations order there are means that doesn't require library support (and this is rarely needed).
D) it's unreasonable to expect all global variables to employ single manager facility within bound any reasonably big application
[...]
It's all beyond the point. The fact that it is possible doesn't mean everybody going to be use it. If I an working in a different department (or just third party), don't use/know your library and present singleton as a part of my interface there is no way for you to refer to it from your singleton destructor, whatever policy you are using to manage you singleton lifetime.
In teardown function (or similar). Explicitly called by user.
Regardless of where the logic is put, if cleanup fails there isn't a whole lot you can do.
There is a whole lot you could do. From simple reporting to enabling end user to resolve the issue somehow.
You pretty much would just have to swallow exceptions or terminate the program anyhow,
So now nobody will notice that you failed to save an important piece of information and/or failure in destructor will crash whole program preventing other components cleanup.
and with destructors you are at least guaranteed that cleanup is attempted.
There are different (automated) means to ensure that cleanup is performed.
Order of global variables destruction in big program linked with modules built by different compilers/versions/options is a dark corner IMO. I am sure there are other points affecting the issue.
What are you talking about? There is one matter of singleton instance access and completely different matter of its methods/state access. *>> No need to synch on instance access at all.*
I do not see a difference at all. To call a method you need an instance (unless you are talking about taking an address of class method)
It seems to me that there exist a general trend to restrict a usage of global variables. And do need to promote bad practices.
I promote an idea to minimize global resource management and resorting to explicit cleanup only if deemed necessary.
rather than expressing dependencies between global resources in a more abstract and scalable way.
Yes. I definitely against expressing dependencies between global resources in any way.
Also in advocating against the use of destructors to perform cleanup.
Destructors should perform trivial/straightforward cleanup Gennadiy
participants (8)
-
Ben Artin
-
Bo Persson
-
Eric Niebler
-
Gennadiy Rozental
-
Jason Hise
-
Martin Wille
-
Michael Goldshteyn
-
Rob Stewart