It is too clear and so it is hard to see.
A dunce once searched for fire with a lighted lantern.
Had he known what fire was,
He could have cooked his rice much sooner.
If the constructor has not been called, the object does not exist. (It doesn't mean anything if you have a "do nothing" constructor and a secondary "init" method. Without the constructor, the "init" method doesn't exist as part of the object.) The existence of memory where an object may eventually reside doesn't advance the notion that you can have an "uninitialized" object. Using a punned pointer to an area in memory where an object, not a "built in", has not been constructed is undefined. (It has been a while. It may be truly illegal instead of just undefined.)It is possible to pre-allocate a buffer, where an object can later be constructed.
This sounds like a major failure of the design.My object that managed [...] thing to be cleaned up.
Let me see if I have this "correct".
1) You have an object (from now called D3DDM) managing ("ACDD" and other facilities) a Direct3D device.
2) You have an object (from now called D3DRS) managing ("ACDD" and other facilities) the rendering states of the device managed by D3DDM.
3) D3DDM has no knowledge of D3DRS. D3DRS has only the knowledge of D3DDM necessary to for the facilities relating to D3DDM to function correctly.
4) D3DDM is a singleton, or traditional global, implemented in such a way that the compiler or standard library destroys the object. (A global instance in a translation unit, a singleton using `atexit' to register a "free me" function, or some such facility.)
5) D3DRS is a singleton, or traditional global, implemented in such a way that the compiler or standard library destroys the object and requires, as a client of D3DDM, that D3DDM is "ready to go" during the use but not necessarily during construction. (A global instance in a translation unit, a singleton using `atexit' to register a "free me" function, or some such facility.)
Under the assumption that my understanding is correct, the only way I see this failing (at any point of execution) is that if both objects aren't treated as potentially uninitialized (not constructed) objects, a broken `atexit' (or similar) implementation, or badly designed objects. (If either objects isn't treated as a true singleton. If `atexit' doesn't apply the functions in reverse order they were registered. If any object managing, exporting, more than one "globally accessible facility with state" treats them as independent during creation but dependent during destruction.)
D3DRS, as an eventual client of D3DDM, must assume that D3DDM is in a valid state as long as D3DRS exists. D3DDM, if treated as a true singleton, must necessarily exist at least as long as its oldest client. I want more information about this.
Soma
ACDD: Allocation Construction Destruction Deallocation
My object that managed various D3D render states was a singleton which worked great....until shut down. At shut down the object managing the device would cleanup the device long before the singleton would get cleaned up. So even though the render state object would attempt to clean up correctly release had already been called on the device. At this point the device was in an invalid state and leaked every COM object that was allocated by the singleton object. Not good. Hard to avoid though since if the device is cleaned up before any other D3D COM allocations the allocations will be leaked. The device must be the last thing to be cleaned up.
You seem to be arguing semantics, when my point was about function. Whether you call a preallocated block an uninitialized object or not, it does conceptually provide such an interpretation. Which goes to my larger point that C++ has no lack of power.
Undefined is illegal. It's the harshest criterion on runtime behavior in the standard.
It is too clear and so it is hard to see.
A dunce once searched for fire with a lighted lantern.
Had he known what fire was,
He could have cooked his rice much sooner.
Function? An object doesn't exist until it is created. An object doesn't exist after it is destroyed. An object that has not been created can not be uninitialized because it doesn't ........ing exist. That is the functionality.
End of story.
Undefined is undefined. Illegal is illegal.
Soma
Speak with some code cause I think you are just using different words for the same thing.
The point is that an uninitialized object can exist.
I have to say that someone can argue thought that the above is not true for an object statically allocated, but only for an object dynamically allocated. Because as initialization one could count allocation as well. Thus considering an object initialized when memory for all its member is allocated (w/o a specific value give).
In this case, MK27 has a point, and you could only achieve that with a pointer and dynamical allocation. And the whole problem is indeed a scope problem, since you lack the ability just to declare an object for scoping reasons. That could be possible, but don't know how useful...
If you define it that way, then it's semantics. And beside the point. Logically a buffer allocated for later construction is an uninitialized object. It's something someone can use to get the functionality they want when they ask questions like in the OP. (not that doing so is a good idea for the OP)
I suppose then by illegal you mean the compiler is required to give an error diagnostic. This won't happen from trying to access a pre-allocated buffer, because doing so requires a reinterpret_cast, which is allowed.Undefined is undefined. Illegal is illegal.
Soma
No, you can pre-allocate a buffer on the heap, on the stack, or in global memory. It's useful for memory pools and things like std::vector. The buffer will generally have to be a char buffer if on the stack, but the size_t version of new returns a void * that can be used. Then placement new is used to call the constructor.
Last edited by King Mir; 03-23-2010 at 06:01 AM.
It is too clear and so it is hard to see.
A dunce once searched for fire with a lighted lantern.
Had he known what fire was,
He could have cooked his rice much sooner.
The problem is that when using the singleton pattern you cannot rely on destructors for proper cleanup if those singletons make use of other singletons or pointers to objects managed by other singletons for their operations. You can end up with dangling pointers and references quite easily during cleanup. This is because the order of cleanup is not guaranteed. To get around this you would implement a cleanup function that would be called prior to actual object destruction which would then allow your system to order the cleanup correctly. Most game design books recommend not relying on constructors for any init besides operations that cannot fail and recommend not relying on destructors for object cleanup b/c the order of cleanup becomes undefined to the programmer since the compiler controls the order.
Here is a good article on the issue at hand:
http://www.research.ibm.com/designpa...s/ph-jun96.txt
But back to constructors and initialization:
Essentially relying on constructors for initialization is ok given that none of the init operations can fail. If they can fail your only option is exceptions which work fine when handled correctly and propogated correctly but trying to throw exceptions across a module boundary is fraught with problems. It can be done given certain restrictions are met. However given the nature and design of DLLs is to act as units of logic, IE: libraries, that can be replaced long after the release of a product there is no guarantee that the restrictions are going to be met. Assuming the user of the DLL and the DLL are both compiled on the same compiler version is a huge assumption in most complex systems.
Just because an object has been created in memory does not guarantee it is ready for use unless you completely init the object in the constructor which means you may have to end up throwing a lot of exceptions from it and using std::auto_ptr or boost pointers to ensure memory allocated prior to the throw point is cleaned up. There are many times when objects may require a significant amount of setup prior to usage that may or may not be done in the constructor. Sometimes at the point of construction the object cannot be setup completely for usage or perhaps it is unwieldy to pass in a whole bunch of parameters to the constructor just to 100% init the object for use. Now in the constructor you can set pointers to 0, values to 0, etc, etc. which means the object has been properly constructed....but the pointers that are 0 cannot yet be used. They have just been init to 0 but they don't point at anything meaningful and b/c of this no function that relies on these pointers can be called. This is the 'invalid state' that has been talked about and it is definitely a problem with the two step approach. However complete init in the constructor is fraught with just as many problems so it's really a matter of picking your poison b/c neither approach is 100% satisfactory in all cases.
Last edited by VirtualAce; 03-23-2010 at 11:36 PM.
Unfortunately I've had enough beating my head against the wall, I guess you just have to continue that nonsense without me.If you define it that [...] questions like in the OP.
From my post:The problem is that [...] since the compiler controls the order.
It is perfectly safe to rely on destructors for proper cleanup. For a singleton you only need to control when the destructors are called. This could be said to be the same as a "destroy" method and a separate destructor. I'm not going to argue with that beyond saying that, in a singleton, neither is a necessary, or valid, part of the public interface.the only way I see this failing (at any point of execution) is [...] a broken `atexit' (or similar) implementation[...].
I have no interest of an article written about the implementation of singletons in C++ from before the standard was published. That said, he article has several errors and invalid techniques. (An example would be "double checked locking".)Here is a good article on the issue at hand:
This is true, but has nothing to do with singletons. Or, rather, the problem isn't innate to singletons.If they can fail your only [...] with problems.
And yet, since there is no standard ABI, a perfectly valid assumption. Even something as simple as a single option to the compiler during compilation of either the library or the client can break compatibility if not used during the compilation of both the library and the client. If you can't assume a compatible ABI you can't export any C++ object through the library. If you can assume a standard ABI, and the ABI allows "thread jumping", you can safely throw exceptions from library to client and back.Assuming the user of the DLL and the DLL are both compiled on the same compiler version is a huge assumption in most complex systems.
There is nothing wrong with relying on constructors to do what they were designed to do. This is much the same as destructors. If you need to create an object with a constructor that can raise exceptions while "hampering" the exceptions, you only need to create a new instance of the object within a `try' block.Just because an object has [...] point is cleaned up.
Using exception safe primitives like `std::auto_ptr<???>' is a good idea in general. I can't imagine why you are complaining about it.
There is always at least one less problem with constructors that a separate "init" method has: I must remember to call this separate "init" method. You say "You only need to clearly document that the "init" method must be called."? I say that you only need to clearly document that construction can fail and therefore may raise an exception.There are many times when [...] satisfactory in all cases.
I will never use an object directly that has a two stage "init" process. I always wrap it in a class that does this for me. I'm sure most C++ programmers would eventually.
Also, if an object has to be "100%" initialized for use, and this "initialization" requires many parameters, you can't get around these necessary parameters by separating their assignment into multiple functions. An "init" method doesn't save you from complexity of implementation; it only moves it around.
Soma
We obviously do not see eye to eye here.It is perfectly safe to rely on destructors for proper cleanup. For a singleton you only need to control when the destructors are called. This could be said to be the same as a "destroy" method and a separate destructor. I'm not going to argue with that beyond saying that, in a singleton, neither is a necessary, or valid, part of the public interface.
A few invalid techniques does not invalidate the entire text of the article.I have no interest of an article written about the implementation of singletons in C++ from before the standard was published. That said, he article has several errors and invalid techniques. (An example would be "double checked locking".)
Which is not possible in all circumstances. Unfortunately when you develop on a platform developed by Microsoft they really do not care about the standard or what things should do. It is generally a bad idea to throw exception across a module boundary in Win32. Period. I do not know about other platforms or OS's or how they deal with the issue.There is always at least one less problem with constructors that a separate "init" method has: I must remember to call this separate "init" method. You say "You only need to clearly document that the "init" method must be called."? I say that you only need to clearly document that construction can fail and therefore may raise an exception.
An assumption that could be extremely costly later down the line. You must work within the confines of your target platform. Working against it won't do much good for anyone involved.And yet, since there is no standard ABI, a perfectly valid assumption.
And what is an ABI?
I did not say it was not without it's faults. You are claiming it is an invalid approach and I am saying it is not as straightforward and simple as you make it out to be.Also, if an object has to be "100%" initialized for use, and this "initialization" requires many parameters, you can't get around these necessary parameters by separating their assignment into multiple functions. An "init" method doesn't save you from complexity of implementation; it only moves it around.
I did not say it did. If you notice in my post I make this statement prior to what you have quoted.This is true, but has nothing to do with singletons. Or, rather, the problem isn't innate to singletons.
But back to constructors and initialization:
Anyways I'm done arguing with you over this since there isn't a clear cut answer or a reason to continue. Do what you will but realize that not everything fits into nice little neat molds as you are claiming they do. I would invite you to think a bit before making all encompassing statements about a language that pretty much allows you to do anything you want even if it isn't the best idea in the world.
I offered you a mechanism that is guaranteed to work if you have a standard `atexit' and follow the trivial documentation of "Call the `get_singleton' method in the constructor of dependent objects.".A few invalid techniques does not invalidate the entire text of the article.
An article posing "While this obviates the need for destroyers, the real problem remains. Garbage collection, anyone?" just isn't interesting.
O_oAnd what is an ABI?
Considering the above, that's probably for the best.Anyways I'm done arguing with you over this since there isn't a clear cut answer or a reason to continue.
^_^Do what you will but realize that not everything fits into nice little neat molds as you are claiming they do.
You are the only person who said "nice" or "neat". I offered something that works. You are using something that obviously does not work. Hah! Congratulations on finding an implementation that isn't "nice", "neat", or functional.
Here is an example of why that comment was stupid: "Memory leaks are bad!", a statement falling into the "all encompassing" category and yet absolutely true even if that's what some bozo want's to do.I would invite you to [...] best idea in the world.
Soma
And what is an ABI?At least I'm honest. I'm not going to act as if I know what you are talking about when I don't. Honestly I don't use that term or abbrev. often. I thought you might have meant API and made a typo.O_o
But you are obviously only open to your own thoughts so I will leave you with them.
Last edited by VirtualAce; 03-25-2010 at 05:18 PM.
I'd be careful with such sayings in C/C++. Obviously you can do really bad things™, and the language won't hinder you (eg throwing an exception in a destructor).
With C/C++, you have to remember that the languages gives you the gun, with the safety off! If you don't know how to use, you will eventually shoot yourself in the foot
The language won't stop you from doing that. It just provides you with the gun. The tools!
Except now you've brought up the two-stage construction again, which laserlight pointed out is a double-edged sword.
I don't like relying on singletons at all (some do call it an anti-pattern, after all). After better yet, I like to avoid globals, because there is no way to tell when they're constructed and destructed. I like deterministic order, so I would define everything I could, in order, inside your main function and provide pointers/references to them.
Just a thought.