Sign in to follow this  

advanced c++ memory managment/singleton conflicts

This topic is 4832 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I am using a memory managed base object, mmobject, which uses a ref-counted retain/pool auto-release system, with garbage collection at every entrance to the run-loop. The mmobject is wrapped with a smart pointer, which calls retain/release as necessary. This system has been stress-tested extensively, and works perfectly. The problem comes when trying to inherit a class from both mmobject, and my templated singleton class, which is as follows.
template <class T> class singleton
{
static T *ms_singleton;
public:
void create();
void destroy();
T *getPtr();
};

when you take and then let drop a smart pointer of a singleton instance, depending on the code, either the garbage collector deletes the singleton, or else the singleton is added but never removed from the list of live objects. The essence of the problem is that there is no way for the memory management system to tell if a given object is a singleton or not. I have considered and tried several solutions to this problem, but none have produced the desired results: 1) placing an identically named function in each class to return whether or not it is a singleton, but the C++ runtime tends to crash over this, I guess the runtime does not support this. 2) inheriting singleton from mmobject, and overloading mmobject methods to prevent the singleton from being added to the memory pool, or deriving both from a separate base class with a flag to indicate singletoness, but the diamond-shape inheritance of this theory defeats itself as the constructors seem to be called in an inconsistent order, and it is hit and miss whether singleton or mmobjects implementation will be called. I am at a complete loss now, and it is proving a real hindrance in the design of my engine (not being able to derive singletons from classes inheriting from mmobject), and I would appreciate any help, SwiftCoder

Share this post


Link to post
Share on other sites
Quote:
Original post by thedevdan
You don't need singletons to be managed. They will be deleted when the program ends.
If you allocate anything in their constructor (or create or whatever), you must deallocate the data as well (it will NOT be done automatigically for all intents and purposes).

swiftcoder: You can add the function to all objects fine, you just need to make it virtual, but that sounds like a rather hackish solution.
Personally, I suggest making singletons have a seperate manager, since they don't need the same kind of management that normal objects will have (just delete any data they allocate after everything else has been cleaned up)

Share this post


Link to post
Share on other sites
Seems I did not make my self clear:

Lets say I have a class 'KernelTask' that inherits from mmobject, and I have a class 'InputTask' that inherits from 'KernelTask', but it is also a singleton, so it also inherits from 'singleton'.
Now you have conflicting memory management systems, the singleton expects the programmer to create/delete it ant the start/end of the program, but the mmobject runs garbage collection on all derived objects, and so deletes the singleton every time through the run loop.

As you can see, this only happens with multiple inheritance, but it is very usefull to have singletons that the rest of the engine (such as the Kernel) can regard as memory managed objects, so pointerscan be stored in arrays (i.e. a std::list of tasks, including singleton tasks).

There was some, not very detailed discussion of this issue on the enginuity thread, but they ended up declaring all functions & variables of the Tasks as static, which is an inelegant solution at best.

Extrarius: Declaring the functions as virtual didn't work, as the runtime alternated between calling mmobjects implementation and singletons implementation, seemingly at random, not sure why this is, but I assume that I have not correctly defined the order of inheritance, or something.

Thanks for the input,

SwiftCoder

Share this post


Link to post
Share on other sites
Well, in the case you described, the easiest solution would be to keep the reference count up. Depending on how your system works, you could probably just have the managed singleton store an extra pointer to itself (raising the count) that it sets to 0(or whatever reduces the reference count) in the destroy method. Then you'd have to make sure you destroy all singletons before the final garbage collection.

In fact, if your managed object provides addref and release methods then you could just use those in create/destroy to keep the count up.

Share this post


Link to post
Share on other sites
I think that it doesn't make much sense for a singleton to be in a memory pool either.

For a singleton there is only mean't to be one instance so it should manage its own life-time, doesn't make much sense for it to be any kind of memory pool.

Another way you could do it instead of making all your type's inherit from mmobject to be in a pooling scheme, take it out of the type hierarchy, have some kind of resource manager or some kind of flyweigt scheme handle the creation of instances that return smart pointers to it or something.

Share this post


Link to post
Share on other sites
A singleton is meant to have a global lifetime by virtue of its global accessibility. It makes no sense for it to be a memory managed object.

I really don't see why you are making your tasks be singletons. You don't need a global lifetime for them and you don't need them to be globally accessible. This reminds me of someone else's post actually from a few months back who was doing the same exact thing.

Share this post


Link to post
Share on other sites
Quote:
Original post by antareus
A singleton is meant to have a global lifetime by virtue of its global accessibility.

A singleton is designed to have global accessibility, but you do not necessarily need, or even want, all singletons to exist at all times, especially as some may be mutually exclusive.
Quote:
Original post by antareus
It makes no sense for it to be a memory managed object.

I could not agree more.
Quote:
Original post by antareus
I really don't see why you are making your tasks be singletons. You don't need a global lifetime for them and you don't need them to be globally accessible.

More to the point, why am I making my singletons be tasks? Take input for instance: Input is a singleton that every object can query about the state of the keyboard, mouse, joysticks, etc., but it needs to be a task so that it can update its state every time through the run loop.

Quote:
Original post by Extrarius
Well, in the case you described, the easiest solution would be to keep the reference count up.
In fact, if your managed object provides addref and release methods then you could just use those in create/destroy to keep the count up.

Agreed, however, the singleton class is not supposed to have any idea of the memory management systems existence, and is primarily intended for non-memory managed classes. In addition, I can not derive singleton from mmobject without creating diamond shape inheritance in the cases I am trying to fix, and I am trying to find a more elegant solution than pure virtual classes.

Keep the good advice flowing...

SwiftCoder


[Edited by - swiftcoder on September 18, 2004 8:34:19 PM]

Share this post


Link to post
Share on other sites
I tend to create my singleton template class with a static internal variable instead of a pointer. Automatic ctor/dtor calling. You could use placement new/delete to reinitialize it as well if you so desired:


#include <new>
template < typename T > class singleton_handle
{
static T data;
public:
T & operator* ( void ) { return data; }
T * operator->( void ) { return &data; }
void remake( void )
{
data.~T();
new (&data) T();
}
template < typename Arg1T > void remake( const Arg1T & Arg1 )
{
data.~T();
new (&data) T( Arg1 );
}
template < typename Arg1T , typename Arg2T > void remake( const Arg2T & Arg2 )
{
data.~T();
new (&data) T( Arg1 , Arg2 );
}
};
template < typename T > T singleton_handle<T>::data;

useage:

class CMySingleton { ... };
typedef singleton_handle< CMySingleton > MySingleton;
//alternative: singleton_handle< CMySingleton > mySingleton;
//removes the need to create a MySingleton instance every place you use it.

void whatever ( void )
{
MySingleton mySingleton;
mySingleton->CallFunction();
}
void pie ( void )
{
MySingleton mySingleton;
mySingleton->CallOtherFunction;
}
//if CMySingleton was a logger and you wanted to refresh it to change buffers:
void switch_to_buffer( const std::ostream & os )
{
MySingleton mySingleton;
mySingleton.remake( os ); //calls ~CMySingleton() then CMySingleton( os )
}

Share this post


Link to post
Share on other sites
MaulingMonkey: Yes, storing the singleton variable as by value would prevent the memory manager from picking it up (memory manager ignores all objects on the stack), and this is, so far, the closest to what I want to do, so I may end up using it.

However, if I have read this correctly, because the data variable is static, it will never be deleted (unless you set data to 0 in the destructor of singleton_handle), and it also means the only way to delete the singleton is to let the singleton_handle go out of scope, which means we are back to scoping variables, which is exactly what the singleton and memory manager are trying to automate in the first case.

Apologies if I am being harsh on people's programming practices and software engineering, but I really would like to construct an as near to fool-proof design idiom for my game engine (and potentially everyone's future projects) as is possible, and having originally learned to program using higher-level languages with nearly-perfect memory management, I am sure there must be an answer that does not use incorrect, slow, or downright bad programming methods such as diamond-inheritance, or rtti.

Sorry for running on...

SwiftCoder

Share this post


Link to post
Share on other sites
Quote:
Original post by swiftcoder
However, if I have read this correctly, because the data variable is static, it will never be deleted (unless you set data to 0 in the destructor of singleton_handle), and it also means the only way to delete the singleton is to let the singleton_handle go out of scope, which means we are back to scoping variables, which is exactly what the singleton and memory manager are trying to automate in the first case.


You have misunderstood some.

Because the data variable is static, you can't call delete on it. You can, however, explicitly call it's destructor (which exists for all classes), and do a placement new (calling it's constructor if it has one). The area of memory is pre-allocated and is freed up at program termination, just like any global variable (or any variable on any modern OS). It's constructor does get called* (on GCC I believe it's at the first time the static variable is ever used) and it's destructor will get called* when the program exits.

* by "will get called" I mean "if things don't seriously fubar in the program, and your compiler isn't having problems combining internal initilization/cleanup code accross DLL and EXE boundries."

singleton_handle goes in and out of scope - because it's static it's shared between all the handles.

The one problem I'm aware of with this method is the order of initialization and deinitialization - if one singleton depends on another things can get messy real fast (when one gets uninitialized before the other, usually). To explicitly (de)initialize the array, you'd need to change the definitions to something like this:


#include <new>
class singleton_handle< typename T >
{
static char[ sizeof(T) ] c_data;
public:
//note: init nor fini check if the handle has been initialized or not yet.
void init ( void ) { new ((T *)c_data) T(); }
template < typename Arg1T > init( const Arg1T & Arg1 ) { new ((T *)c_data) T( Arg1 ); }
void fini ( void ) { ((T *)c_data)->~T(); }
//note: this will fubar if things havn't been initialized properly.
T & operator*( void ) { return *((T *)c_data); } //prevents casting to (const) temp var.
T * operator->( void ) { return ((T *)c_data); }
};
template < typename T > char singleton_handle<T>::c_data[ sizeof(T) ];


edit: deleted ampersands (&) that didn't belong.

Share this post


Link to post
Share on other sites
Here's my favorite singleton, I've found there really isn't many scenarios I've found where it doesn't fit:


template<class T>
class singleton
{
public:
singleton()
{
assert(m_ptr == 0);
m_ptr = static_cast<T>(this);
}
~singleton()
{
assert(m_ptr != 0);
m_ptr = 0;
}
T& get_instance()
{
assert(m_ptr != 0);
return *m_ptr;
}
private:
T* m_ptr;
};

template<class T>
T* singleton<T>::m_ptr = 0;



To use, you just inherit from singleton<YourClass>.

This way, you can manage the lifetime of the singleton exactly as you would an automatic variable. Just make instances of your singleton classes right inside your main() function, and those variables can now be accessed by every part of the program. This also allows you to control the order of initialization and destruction.

This way, your singleton class wouldn't even have to know your mem mgr class exists.

Share this post


Link to post
Share on other sites
Quote:
Original post by swiftcoder
[...]I have a class 'InputTask' that inherits from 'KernelTask', but it is also a singleton, so it also inherits from 'singleton'.
Now you have conflicting memory management systems [...]


Make your memory manager to delete singletons at the end of memory manager's life time.

Share this post


Link to post
Share on other sites
Quote:
Original post by daerid
Here's my favorite singleton, I've found there really isn't many scenarios I've found where it doesn't fit:
*** Source Snippet Removed ***
To use, you just inherit from singleton<YourClass>.

This is actually the same singleton I am using, I just added create and destroy as wrappers around new and delete, and to do some debug checking, and I only implement m_ptr in the source files I need to use it in:

// Task.h
class Task : public Singleton<Task>, public mmobject
{
...
}
// task.cpp
Task *Singleton<Task>::m_ptr = NULL;
// which allows the singleton to be used without too many
// problems in any flavour of dynamic library.


Quote:
Original post by daerid
This way, you can manage the lifetime of the singleton exactly as you would an automatic variable. Just make instances of your singleton classes right inside your main() function, and those variables can now be accessed by every part of the program. This also allows you to control the order of initialization and destruction.

This way, your singleton class wouldn't even have to know your mem mgr class exists.

This is how I was trying at first, but because the singleton is created using new, the memory manager picks it up as dynamically allocated, and places it in the object pool, and when the smart pointers go out of scope, the singleton tends to get released. I am thinking of going back to this method, and just not worrying about it, i.e. just let the singleton go out of scope when all smart pointers have gone out of scope, since in most cases where a singleton is memory managed, it does not need global lifetime.

Now another question, in the above code snippet, where Task inherits from both singleton and mmobject, if both classes have an identically named virtual method, which classes version of the function will be called (i.e. if singleton and mmobject both had a method: virtual void release(); and you call Task->release(); which one will be called, assuming that Task has not overridden the release()?).

Thanks,

SwiftCoder

Share this post


Link to post
Share on other sites
The singleton-ness is screwing you over here. Why not just pass parameters around as needed and let things like Input just remain a task? What I'm saying is that I don't see how it is desirable that *any* object can get access to the input.

Share this post


Link to post
Share on other sites
In your singleton, instead of doing:
m_ptr = new T;

try this:
m_ptr = ::new T;

and for delete:
::operator delete(m_ptr);

This will force the invocation of the global new operator (and I assume you just override a new/delete operator in mmobject). So, as long as the global operators aren't overridden (which is often a bad idea anyway), should be fine.

Share this post


Link to post
Share on other sites
Quote:
Original post by Desert Fox
In your singleton, instead of doing:
m_ptr = new T;
try this:
m_ptr = ::new T;
and for delete:
::operator delete(m_ptr);


Perfect, This just what I was looking for, twenty minutes after i realised that my original system worked fine, and that I have been talking myself around in circles :)
Thanks anyway.

I had just realised that this whole thread was a waste of everybody's time, because of the very reason i posted it, namely that if the singleton also inherits from the memory managed object, it obviously wants to be garbage collected, and doesn't need to exist for the whole lifetime of the program.
i.e. in the case of the Input Task, as soon as the Kernel lets the reference to it go out of scope, it obviously is not needed any more, so we just let garbage collection delete it, instead of having to call delete manually (and it can always be created again if necessary).

I still can't believe how unbelievable stupid I was not to see this immediately, it was staring me right in the face.

Many thanks for all you input (which made me truly think about this),

SwiftCoder

Share this post


Link to post
Share on other sites
That's great that you found the solution. Just to clear things up, the singleton I posted doesn't require you to instantiate it by using operator new. You allocate the object on the stack, usually directly in the main() function, and the singleton template allows you to access it in any function below it.

Share this post


Link to post
Share on other sites

This topic is 4832 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this