Skip to main content
GameDev.net gamedev.net
🔒 Locked

[Solved] Clean memory allocation

Started by spudmuncher Mar 13, 2009 at 10:02 PM 24 replies 3.5k views
Original Post
spudmuncher
spudmuncher
Hi, I have no idea what the best way to deal with memory allocation is. I'm using vc++ express. I want to be able to, CDevice * d = createDevice (); and then, d->drop (); and I want all devices to be automatically updated under a single function call run (), while (run ()) { /*u get the idea*/ } I've written my attempt at doing this, using c++ new and delete, but it's problematic, because it requires a huge tonne of code devoted to each and every object that I want to have those features. I can post it if anyone wants, but it's a bit long so I won't post it now. My method is to keep a list of pointers to all the objects of a type (type is passed using an enum) for every type. That list is updated dynamically so it can have an unlimited number of list items. Every time an object is created, the list is given the pointer and the type to add. Then the list will call the updt() member of every object in the list. There has to be a better way. A way that doesn't involve object type specific code. It would be great if I could just have a base class that deals with reference counting / dynamic memory. that way, class CDevice : public CRef will be just about sufficient. Sorry if I'm hard to understand, this is a hard topic to explain. A link to an article on this very topic would be tops! Thx [Edited by - spudmuncher on March 28, 2009 10:14:03 PM]
Captain P
Captain P
Create a Device base-class, with a virtual Update() function (and a virtual destructor), and derive your specific devices from that class. Then, have a container of Device pointers and store all devices in it. For updating, you can iterate through the container and call the Update() functions.

As for memory management, if you want to delete a device, you can call delete on a Device pointer, and since the destructor is virtual, the destructor of the actual type will be called (for example, DerivedDevice::~DerivedDevice) - you don't need to do manual bookkeeping for that anymore.


The idea here is hat Device is a generic interface, that is the same for all device types. So, regardless of the actual type, you can all treat them the same. What they do under the hood depends on their actual type (and that's what the virtual keyword is for).

Does that solve your problem?
spudmuncher
spudmuncher
Thanks a lot for your response.
I've tried to make a concept design of how it will work, based on what you said. But I'm new to these advanced C++ concepts because I haven't needed to use them until now, so please let me know if I've done something (or everything) erroneously. Until today I couldn't imagine a possible use for polymorphism (:

// generic object base classclass CRefObject{public:  virtual ~CRefObject ();  virtual void update ();};// holds a reference to the pointers of all objects (all are the same type, CRefObject)class CPointerContainer{  // list of pointers is dynamically allocated, so it can have unlimited items  CRefObject ** pointer; // ** because array of pointers  int nPointers;public:  ~CPointerContainer ()  { // destroy all    for (int i; i<nPointers; ++i) delete pointer[];  }  add (CRefObject * ptr);  // won't bother adding insides of this function, basically it adds the given pointer to the container  remove (CRefObject * ptr); // again, won't include insides of this function.    							//It searches for the pointer in the list, and then deletes that pointer, setting it to null  clean (); // cleans up the container, slides upper pointers in the list down over null pointers  updateAll ()  { // this would be called in a main loop    for (int i; i<nPointers; ++i)    {      if (pointer != NULL) pointer->updt (); // a null pointer is null because it has been deleted    }  }} container;virtual void CRefObject:: update (){  // default update action when not specified} // allows derived classes to be cleaned, when the base class is destroyedvirtual CRefObject:: ~CRefObject (){  container.remove (this);}// a couple of derived devicesclass CDeviceA : public CRefObject{public:  ~CDeviceA () {};  void update () {/* do the update stuff for this device */};};class CDeviceB : public CRefObject{public:  ~CDeviceB () {};  void update () {/* do the update stuff for this device */};};// when I want to create a device I would call this function, which returns a pointer to that deviceCDeviceA * CreateDeviceA (){  CDeviceA * d;  d = new CDeviceA;  container.add (d);  return d;}CDeviceB * CreateDeviceB (){  CDeviceB * d;  d = new CDeviceB;  container.add (d);  return d;}// so in the main loop I can now do thisvoid run (){  CDeviceA * device;  device = CreateDeviceA ();  container.updateAll ();  delete device;}


[Edited by - spudmuncher on March 15, 2009 5:37:21 AM]
KulSeran
KulSeran
Quote:

Code: (grrr, neither tabs nor spaces seem to work here to indent stuff )

[code][/code]
or
[source][/source]


1) pure virtual is good for interfaces
class CRefObject{  public:  virtual ~CRefObject (){};  virtual void update () = 0; // << pure virtual means you HAVE to implement this (helps prevent typos like "Update()" from working)  virtual bool dead() = 0;  void deleteMe() { delete this; }};class CDeviceA : public CRefObject{  virtual CDeviceA( /* params */ ) { deviceResources->aquire(); }  virtual ~CDeviceA() { deviceResources->release(); }  virtual void update() { /* do stuff! */ }  virtual bool dead() { /*decide when we are good for removal*/ }}


2) std::vector<> is your friend.
std::vector<CRefObject *> updateList;updateList.push_back( new CDeviceA (/* device A params */) );//...while(running()){   std::for_each( updateList.begin(), updateList.end(), std::mem_fun(&CRefObject::update) );  //...  std::vector<CRefObject *>::iterator i = std::remove_if(updateList.begin(), updateList.end(), std::mem_fun(&CRefObject::dead);  std::for_each(i, updateList.end(), std::mem_fun(&CRefObject::deleteMe);  updateList.erase( i, updateList.end() );}std::for_each(updateList.being(), updateList.end(), std::mem_fun(&CRefObject::deleteMe);updateList.clear();



3) As in the source above, prefer a "is dead" and centralized removal. It allows you to avoid all the random edge cases that tend to pop up when you add/remove items during the update loop.

4) The CRefDevice should probably inherit from boost::noncopyable under the assumption it aquires some resource that needs removal on delete.

** NOTE!: there could be typos, I typed the code off the top of my head.

[Edited by - KulSeran on March 15, 2009 3:48:55 AM]
spudmuncher
spudmuncher
Please excuse me for seeming noobish, but....
I didn't really understand what you said KulSeran. Could you try to relate it more specifically to what I wrote. I noticed you used a boost library command, but I'm not using boost. And how does the standard library vector have to do with what I'm doing. Do we have to use external stuff. I guess if it's really worthwhile using std::vector<>, could you please explain how it could fit in with the code I wrote. I'm also a bit confused about the way you changed my code in step 1. Sorry. Thx
KulSeran
KulSeran
Well, for step 1, I added a "= 0" at the end of the definition of the virtual update function. This tells the compiler that CRefObject does not in fact implement update. So you can't make a CRefObject on its own, and whatever classes inherit from CRefObject are forced to implement update().

For step 2, I didn't use a single boost function. And i replaced your CPointerContainer with std::vector<>.
Your add, remove, and clean functions for that CPointerContainer all require from the looks of it
1) you allocated enough memory to start with
2) you have to iterate over all objects just to find some free space
3) you have to constantly check "is not null? ok do stuff." instead of knowing the container only holds valid pointers.

by replacing your container with the C++ standard container you get:
clarity.
1)speed (no more iterating compacting NULLs, or finding a NULL to use as free space)
2)no need to know how much memory to new/delete for your container.
3)access to the standard library algorithms and utilities.



I re-read your posts, maybe a full example of how I'd do it that you can actually run will help:
#include <vector>#include <algorithm>#include <functional>//----------------------------------------------------------------------------// Your device interfaceclass DeviceBase{	public:		DeviceBase(){};		virtual ~DeviceBase(){};		virtual void update () = 0; // << pure virtual means you HAVE to implement this (helps prevent typos like "Update()" from working)		virtual bool isDead() = 0;		void deleteMe() { delete this; }};//----------------------------------------------------------------------------static std::vector<DeviceBase *> updateList;static std::vector<DeviceBase *> removeList;//----------------------------------------------------------------------------// Adds it to the update listvoid AddForUpdate( DeviceBase *pDevice ){	updateList.push_back( pDevice );}//----------------------------------------------------------------------------// Flag it for later removal.// Don't outright remove it, incase this is called durring updatevoid FlagForRemove( DeviceBase *pDevice ){	removeList.push_back( pDevice );}//----------------------------------------------------------------------------// Check if it is in the remove listbool WasRemoved( const DeviceBase *pDevice ){	return std::find( removeList.begin(), removeList.end(), pDevice ) != removeList.end();}void RemoveDeleted( ){	std::vector<DeviceBase *>::iterator i;									// Iterator	i = std::remove_if(updateList.begin(), updateList.end(), WasRemoved );	// quickly moves all items that "WereRemoved" to the end of the list	updateList.erase( i, updateList.end() );								// quickly pops all items past i	removeList.clear();														// clear our "deleted already" list}//----------------------------------------------------------------------------// Clear out the listvoid ClearDevices(){	//_______________	// first kill all the stuff that was already deleted	RemoveDeleted();	//_______________	// now kill everything else	std::for_each(updateList.begin(), updateList.end(), std::mem_fun(&DeviceBase::deleteMe));	// destroy the objects	updateList.clear();	removeList.clear();}//----------------------------------------------------------------------------// Update loopvoid UpdateLoop( ){	//_______________	// first kill all the deleted stuff	RemoveDeleted();	//_______________	// Check for things that died	std::vector<DeviceBase *>::iterator i;									// Iterator	i = std::remove_if(updateList.begin(), updateList.end(), std::mem_fun(&DeviceBase::isDead));	std::for_each(i, updateList.end(), std::mem_fun(&DeviceBase::deleteMe));	updateList.erase( i, updateList.end() );	//_______________	// Now update	std::for_each( updateList.begin(), updateList.end(), std::mem_fun(&DeviceBase::update) );}//----------------------------------------------------------------------------// Inherited deviceclass DeviceA : public DeviceBase{	public:		DeviceA() : _dead( false )		{			AddForUpdate( this ); // add for update		}		virtual ~DeviceA()		{			FlagForRemove( this ); // add for remove		}		// virtual update function, so we don't have to know what type of device in the update loop		// but the right code gets called		virtual void update()		{			std::cout << "Device A Updating" << std::endl;		}		virtual bool isDead()		{			return _dead;		}	private:		bool _dead;};class DeviceB : public DeviceBase{	public:		DeviceB() : _dead( false )		{			AddForUpdate( this ); // add for update		}		virtual ~DeviceB()		{			FlagForRemove( this ); // add for remove		}		// virtual update function, so we don't have to know what type of device in the update loop		// but the right code gets called		virtual void update()		{			std::cout << "Device B Updating" << std::endl;		}		virtual bool isDead()		{			return _dead;		}	private:		bool _dead;};//--------------------------------------------------------------// Begin Test Func//--------------------------------------------------------------int main ( u32 argc, char *argv[] ){	//----------------------------------------------------------------------------	// create some devices	DeviceBase *newDevice1 = new DeviceA;	DeviceBase *newDevice2 = new DeviceA;	DeviceBase *newDevice3 = new DeviceB;	// Test the update loop	UpdateLoop();	// Delete one	delete newDevice1;	// Test again	UpdateLoop();	// Clear out everything	ClearDevices();	//----------------------------------------------------------------------------	return 0;}

* and all of this is just out-of-the-box C++. No boost:: or other libraries.

[Edited by - KulSeran on March 15, 2009 10:31:04 PM]
spudmuncher
spudmuncher
Thanks KulSeran for explaining in more detail.
That is far more helpful (:
When I get over this annoying flu I'll have another attempt at it.

Judging by the way certain things looked slightly mysterious, I think I will have to revise my c++ skills.

I'll probably put in a using namespace std;
to make the code look cleaner too (;



A question, are

DeviceBase *newDevice1 = new DeviceA;

and
DeviceA *newDevice1 = new DeviceA;

both valid and equivalent
loufoque
loufoque
Quote:
I have no idea what the best way to deal with memory allocation is.

RAII is the way to go in C++. Learn it, it's the basics.
spudmuncher
spudmuncher
RAII seems very complex.
http://www.codeproject.com/KB/cpp/RAIIFactory.aspx
An OO Variant
Is the only information I've found that is even remotely applicable to my problem. I am struggling to see how to connect it to the work I've done.

So, as I understand it

I would have my generic base class for all devices
class DeviceBase;

Then I would declare the RAII factory
class DeviceFactory;

Then I would have all my little devices
class DeviceA : public DeviceBaseclass DeviceB : public DeviceBase;


Also, is there a way I can manually control the factory life-time, rather than having it only be valid for a single portion of my code (i.e. my devices need to be accessible from a lot of places). I can't seem to find a way to manually remove specific items from the factory either, eg. DeviceA->Drop ();

In the code I wrote, I didn't use a constructor in the base class. And when I create a new DeviceA I would put in new DeviceA (arg1, arg2);. How is this still possible with RAII though.

in MyPolymorphicFactory
void myFun (int n){  MyPolymorphicFactory factory;  for (int i = 0; i < n; ++i)  {    MyBase* m = factory.create<MyBase> (n);    // ...    m = factory.create<MyDerived> (n);    // ...  }

what exactly is MyClass in RaiiFactoryImp imp;
Is that some sort of keyword.
Captain P
Captain P
I think that article is confusing matters with their 'RAII factory', and the fact that it discourages the use of smart pointers does not speak in it's favor (smart pointers are a fine example of RAII!).

The point of RAII is not to create 'factories' and all that. It's simply to tie resource allocation/deallocation to the lifetime of objects. For your device classes, their constructors create or open the underlying device, and their destructors should ensure that the underlying device is properly disposed of. That's what RAII is about.


So it looks like you're already using RAII for your devices. But, RAII can also be applied to dynamically created objects, by using smart pointers to encapsulate them. If a pointer goes out of scope, it is destroyed, but the object it pointed to is not. A smart pointer makes sure that the object it points to does get destroyed (it does so in it's destructor). There are various types of smart pointers, too, for various kinds of behavior: boost::shared_ptr only removes the wrapped object once all copies of the shared pointer are destroyed, while std::auto_ptr simply removes it's wrapped object when it is destroyed (useful to tie a dynamically allocated object to a scope).

So, to fully automate the memory management, you could use a std::vector of boost::shared_ptr's to Devices:

std::vector< boost::shared_ptr<Device> >   devices;devices.push_back(boost::shared_ptr<Device>(new Device(...)));

Upon removing a shared-pointer-to-Device from the vector, if that was the last shared pointer that pointed to that Device, the Device will automatically be deleted.
NotAYakk
NotAYakk
Don't be afraid of boost -- at least not the shallow parts of boost.

Most of the shallow parts of boost are going to be added to C++0x -- the upcoming version of C++ that your compiler is going to support in a year or three. They are that solid.

---

A std::vector is a way to implement a dynamic array of data without having to manage it manually.

std::vector is a class that owns an array of foo's. When the object goes away, the array is automatically deleted. You can insert in the middle (it shuffles the items for you), you can add to the end (it manages memory for you, and even 'overallocates' to reduce the number of reallocations you have to do), etc.

---

The std library is 'iterator' based. iterators are things that _act like_ pointers -- ++ works, * works, -> works, etc. They are anything the implementor wants them to be under the hood. This lets your code look the same when iterating over an array, or a linked list, or a red-black search tree. With templates, it means that you can actually use the exact same code for all of those containers.

The std library algorithms put out bits of code that tend to be widely used.

Look at this one:
return std::find( removeList.begin(), removeList.end(), pDevice ) != removeList.end();


I'll break it down:
typedef std::vector<DeviceBase *> DevicePtrArray;DevicePtrArray removeList;bool is_on_removeList( DeviceBase* pDevice ) {  // An iterator is something that acts like a pointer to something in a container:  DevicePtrArray::iterator it;  // Here we use the std::find algorithm:  it = std::find( removeList.begin(), removeList.end(), pDevice );  // See the begin() and end() calls?  That gets an iterator to the start and  // one-past-the-end of the container.  std::find contains code that looks  // like this:  // iterator find_psuedocode( iterator begin, iterator end, data find_this )  //    // Note that begin and end together describe a range of elements!  //    // So, for each element in the range:  //   for (iterator i = begin; i != end; ++i) {  //     // if the thing we are supposed to look for is here:  //     if (find_this = *i) {   //       return i; // return the iterator (ie, fake pointer) to the element  //     }  //   }  //   return end; // if we didn't find it, return the iterator at the end  //   // note that we never look at *end -- the end pointer tells us when we have  //   // LEFT the valid range of elements.  // }  // Now, why not just write the above code?  Because std::find is typo-free,  // contains no bugs, and is shorter, and is written using as many speedup tricks  // as they could cram in.  // Ok, next, we see if we found the element.  If we did, we return true, otherwise...  return it != removeList.end();}


There I did only one thing per line. As it happens, one of them was running a complete algorithm.

None of this uses boost.

Boost shows up at this point when you want to deal with object lifetimes.

boost::shared_ptr< foo > creates a shared pointer to foo. A shared pointer is a class that acts like a pointer (* works, -> works -- but as this isn't an array, ++ is disabled). If you are managing the lifetime of foo via shared_ptr, every 'long term' pointer to the class should be a shared pointer.

Shared pointer keeps track of the number of pointers to a given object, and when you run out, deletes the object. You can also tell the shared pointer to call a different function when it runs out (which is quite useful).

Going deeper down the rabbit hole, there are weak pointers -- weak pointers are shared pointers that don't own a reference. You claim a temporary reference from them by calling .lock() on the weak pointer. If it returns a NULL shared pointer, the data had been deleted elsewhere. Otherwise, you now have a temporary 'real' reference, and the data will stay around as long as you own it.

Between the two of them, you can pull off some neat stuff.

If I where to solve this problem, I'd have shared pointers of devices being passed around. You destroy a device by losing the last shared pointer to it. The 'foreach device' container would contain weak pointers.

The foreach device code would lock() the weak pointer, get a shared pointer, then tell it to update. If the lock() returned a null pointer, it'd skip it.

Then it would do a std::remove_if on 'the weak pointer is NULL', and then erase the useless pointers from the foreach container.

I wouldn't advise you to go this far, because there are some subtle tricks and issues at this depth.
spudmuncher
spudmuncher
Thx NotAYakk, that was fantastically insightful.

The main reason to be afraid of boost is that it's a big dl, and can be difficult to implement properly in the IDE. I may have a version of it lying around somewhere in my downloads folder though, just got to try and get it operating with vc++ (: Hopefully won't be a problem.

So, after the inner workings are sorted, just to use my classes in the main loop it should be possible to have it be this simple:

DeviceA * d1, * d2; // global scope, likely extern somewherevoid RunMainLoop (){  d1 = CreateDeviceA ();  delete d1;  d2 = CreateDeviceA ();  UpdateAll ();  delete d2;}


"You destroy a device by losing the last shared pointer to it"
Can I explicitly
delete
the shared pointer to do that? I can't deal with using only scopes, because that wouldn't work with the above model I want.

"I wouldn't advise you to go this far, because there are some subtle tricks and issues at this depth."
Ouch. I'll need to go that far though to solve my problem wouldn't I !?!?

P.S. I like the way the boost::shared_ptr is reference counted. I want to log things that happen to a particular device in a .txt file, but I may very well have multiple devices running simultaneously. If I could extract that reference number, I could add it to the file name, ie. "device1_log.txt".
spudmuncher
spudmuncher
Ah, KulSeran's example makes more sense now.

I'm confused about exactly what headers I need to include
to use
1. vectors
2. boost::shared_ptr
3. maybe weak_ptr
spudmuncher
spudmuncher
Ok, shamelessly stealing and rewriting KulSeran's example.
How is this? Is it sufficient or should it be modified some way.
Let me know if I've done anything seriously wrong, or if this isn't
efficient enough for practical use.

Thx for all of your help. I've learnt a lot!

#include <iostream>#include <vector>#include <algorithm>#include <functional>//----------------------------------------------------------------------------// BaseClass//----------------------------------------------------------------------------class BaseClass{	bool is_dead;public:	BaseClass ()		{ is_dead = false; }	virtual ~BaseClass () {};	virtual void Update () = 0;	bool IsDead () { return is_dead; }	void SetIsDead (bool d) { is_dead = d; }	void DeleteMe () { delete this; }};//----------------------------------------------------------------------------// MemManager//----------------------------------------------------------------------------class MemManager // memory manager{	typedef std::vector<BaseClass *> ClassContainer; // pointer array	ClassContainer updateList, // items that will be updated								 removeList; // items that are to be removedpublic:	MemManager () {}	~MemManager ()		{ ClearAll (); }	void AddForUpdate ( DeviceBase * p_device );	void FlagForRemove ( DeviceBase * p_device );	bool WasRemoved ( const DeviceBase *p_device );	void RemoveDeleted ();	void ClearAll();	void UpdateLoop ()} memManager;//----------------------------------------------------------------------------// Add to update listvoid MemManager:: AddForUpdate ( DeviceBase * p_device ){	updateList.push_back( p_device );}//----------------------------------------------------------------------------// Flag for later removal.// Don't outright remove it, incase this is called during updatevoid MemManager:: FlagForRemove ( DeviceBase * p_device ){	p_device->SetIsDead (true);	removeList.push_back( p_device );}//----------------------------------------------------------------------------// Check if it is in the remove listbool MemManager:: WasRemoved ( const DeviceBase *p_device ){	return std::find( removeList.begin(), removeList.end(), p_device ) != removeList.end();}//----------------------------------------------------------------------------void MemManager:: RemoveDeleted (){	std::vector<BaseClass *>::iterator i; // Iterator	i = std::remove_if (updateList.begin(), updateList.end(), &MemManager::WasRemoved );	// quickly moves all items that "WereRemoved" to the end of the list	updateList.erase( i, updateList.end() ); // quickly pops all items past i	removeList.clear(); // clear our "deleted already" list}//----------------------------------------------------------------------------// Clear out the listvoid MemManager:: ClearAll(){	//_______________	// first kill all the stuff that was already deleted	RemoveDeleted();	//_______________	// now kill everything else	std::for_each(updateList.begin(), updateList.end(), std::mem_fun(&BaseClass::DeleteMe));	// destroy the objects	updateList.clear();	removeList.clear();}//----------------------------------------------------------------------------// Update loopvoid MemManager:: UpdateLoop (){	//_______________	// first kill all the deleted stuff	RemoveDeleted();	//_______________	// Check for things that died	std::vector<BaseClass *>::iterator i; // Iterator	i = std::remove_if(updateList.begin(), updateList.end(), std::mem_fun(&BaseClass::IsDead));	std::for_each(i, updateList.end(), std::mem_fun(&BaseClass::DeleteMe));	updateList.erase( i, updateList.end() );	//_______________	// Now update	std::for_each( updateList.begin(), updateList.end(), std::mem_fun(&BaseClass::update) );}//----------------------------------------------------------------------------// ClassA//----------------------------------------------------------------------------// derived classclass ClassA : public BaseClass{public:	ClassA()	{ AddForUpdate( this ); } // add for update	virtual ~ClassA() { FlagForRemove( this ); } // add for remove		virtual void Update();};//----------------------------------------------------------------------------virtual void ClassA:: Update (){	std::cout << "Class A Updating" << std::endl;}//----------------------------------------------------------------------------// ClassB//----------------------------------------------------------------------------// derived classclass ClassB : public BaseClass{public:	ClassB()	{ AddForUpdate( this ); } // add for update	virtual ~ClassB() { FlagForRemove( this ); } // add for remove		virtual void Update();};//----------------------------------------------------------------------------virtual void ClassB:: Update (){	std::cout << "Class B Updating" << std::endl;}//----------------------------------------------------------------------------// Some global functions//----------------------------------------------------------------------------ClassA * void CreateClassA (){	ClassA * c;	c = new ClassA;	return c;}//----------------------------------------------------------------------------ClassB * void CreateClassB (){	ClassB * c;	c = new ClassB;	return c;}//----------------------------------------------------------------------------// Begin Test Funcvoid runMainLoop (){	ClassA * class1 = CreateClassA ();	ClassA * class2 = CreateClassA ();	ClassB * class3 = CreateClassB ();		// Test the update loop	memManager.UpdateLoop();		// Delete one	delete class1;		// Test again	memManager.UpdateLoop();	return;}


Btw, should I have boost in there somewhere, and if so, how would I add it?

[Edited by - spudmuncher on March 18, 2009 1:08:35 AM]
NotAYakk
NotAYakk
If you have a pointer to an object that you DO NOT own the lifetime of, you have a boost::weak_ptr.

If you have a pointer to an object that you DO control the lifetime of, you have a boost::shared_ptr.

boost::weak_ptr must be created from a boost::shared_ptr.

So if your FooClass objects lifetime is determined by a central registry, the central registry owns a boost::shared_ptr. Everyone else owns a boost::weak_ptr.

When they want to access the FooClass, they create a temporary boost::shared_ptr via the .lock() method of weak_ptr. So long as they own this temporary, the lifetime of the FooClass continues (preventing it from being 'deleted out from under you').

#include
#include

So an example of all of this:
template<typename T>class ExistenceRegistry {  typedef std::vector< boost::shared_ptr<T> > element_list;  element_list elements;public:  boost::weak_ptr<T> getNth( int n ) {    if (n >= elements.size() || n < 0) return boost::weak_ptr<T>();    return elements[n];  }  void deleteNth( int n ) {    if (n >= elements.size() || n < 0) return;    elements.erase( elements.begin()+n, elements.begin()+n+1 );  }  void push_back( boost::weak_ptr<T> element ) {    boost::shared_ptr<T> tmp = element.lock();    if (tmp) {      elements.push_back(tmp);    }  }};

The above is a really simple example of a class that manages the lifetime of your elements. Elements removed in deleteNth will have every outstanding weak pointer made invalid in a thread-safe way. (Naturally, you'd have to guard access to the std::vector with concurrency guards in a multi-threaded app).

Note that the implicit cast from weak pointer to a strong shared pointer throws if the weak pointer is null. The .lock() method doesn't throw, and just returns shared_ptr() (ie, a null shared pointer). That often makes it more useful.

weak pointers can do nothing without being turned into shared pointers. This is useful, because without being a shared pointer, the user of the object has no guarantee that some other thread won't destroy the object under them.

The downside to this is that you should use never store a T* anywhere. You can use a temporary T* in a utility function, but it must be derived from a shared_ptr with a guaranteed longer lifetime, and cannot be made to persist.

If you have lots of code that uses T*s (to be clear, I'm using T as a placeholder name for whatever type you are using in the shared/weak pointers) and sometimes ends up deciding to keep them around for a period of time, and it would be annoying to fix, you can use enable_shared_from_this.

#include <boost/enable_shared_from_this.hpp>class Foo: public boost::enable_shared_from_this<Foo> {public:  boost::shared_ptr<Foo> getShared() {    return this->shared_from_this();  }  boost::shared_ptr<const Foo> getShared() const {    return this->shared_from_this();  }};

It uses the 'curiously repeating template pattern" (CRTP) to do some mojo that allows you to get the shared pointer to your class while only starting with a naked pointer, by implementing "intrusive" reference counting.

Usually you can avoid this issue without using enable_shared_from_this, but it can help in the cases where it isn't practical.
Zahlman
Zahlman
Quote:
Original post by spudmuncher
The main reason to be afraid of boost is that it's a big dl


Please note that the Boost libraries are, for the most part, severable - you can pick and choose the parts that you need. Most of it is just implemented with C++ source (or even just headers and template code) that you can simply download and add to your project (although it's usually a good idea to do things in a slightly more organized way :) ).
NotAYakk
NotAYakk
Yep, there are deep and scary depths to boost -- things like ASL, or even (to some) lambda expressions. And don't get me started on how scary the macro metaprogramming in boost is (!)

But other parts of boost are just nice and useful code. So much so that they have already been added to std::tr1 and/or added as language or standard library extensions in C++0x.

In particular, shared pointer and it's cousin weak pointer solve entire categories of problems efficiently, cleanly and without fuss. And they are implemented in header files (as far as I know).

As you run into other problems, you could end up using other parts of boost. But while it is a monolithic download, it isn't a monolithic library.
spudmuncher
spudmuncher
Ok, I'll keep boost in mind. Like I said, I've dl it in the past, but have to find it again and get it to work in vc++.

Anyway...

With my limited view on how I want my code to be
and the model I want, I'm not sure I understand the boost
method. It seems like some sort of radically different
way to manage memory.

My model I wanted from the very beginning was (on the outside):
Device * device = CreateDevice ();device->doSomething ();device->someVar = 5;delete device; // or device->drop ();Device * device2 = CreateDevice ();delete device;


This methodology I understand. It doesn't sound like
it's easy or practical to use this model with the boost stuff,
so what model is recommended?!?
Keep in mind that I'm working with a
friend who would probably want to stick with what he
understands too (which is what I proposed).


A questions which is REALLY important to me:

- Is the last source code section I provided sufficient for
my problem, or is it too inefficient that it would
be responsible to make more of an effort to use the
boost method?

I wouldn't mind learning the boost method if it's the way to go,
but I need to understand the model I'm supposed to use. You've
made it clear the nitty-gritty details on how to do it, so that's
not the problem.
ToohrVyk
ToohrVyk
Quote:
Original post by spudmuncher
My model I wanted from the very beginning was (on the outside):
There's nothing wrong with your model in itself. As a matter of fact, I certainly wouldn't object to having an interface that looks like:

Device &device = createDevice();device.frobnicate();destroyDevice(device);


There are problems here, though.You cannot do this easily if you implement "createDevice()" as a thin wrapper around "new" and "destroyDevice" as a thin wrapper around "delete" because you will run into memory ownership issues and you will forget to release the device. Besides, this also creates, implicitly, a global list of devices to be manipulated, which is bad : global things should always be made explicit.

A clean way of doing this is to think in clean terms instead of inventing your own (for instance, "the list of all devices" makes no sense in C++, but "a list of devices" does make sense).

For instance:

class Device{  // Implement your device here, as a plain old C++ class   // with constructors and destructor.};typedef std::list<Device> Devices;typedef Devices::iterator DeviceHandle;// The devices. You may make this list global if you wish.Devices devices;// Create a new device, add it to the list of devices, get a // handle to itdevices.push_front(Device(/* constructor arguments */));DeviceHandle handle = devices.begin();// Have some fun with the devicehandle -> frobnicate();// Remove it from the listdevices.erase(handle);// Let's traverse all devices and do something to them...for(DeviceHandle handle = devices.begin(); handle != devices.end(); ++handle){  handle -> update();}// Now let's remove all dead devices from the list of devicesdevices.remove_if(std::mem_fun_ref(&Device::isDead));


From what you've explained about your problem, I see no reason to use pointers (boost or otherwise) to solve it. Nor do you need any silly code to handle a container of devices...

Of course, if you wish for your device class to have polymorphic behavior, then you might need some additional cleverness which may involve boost::shared_ptr.

For instance:

template<class T>class Proxy{  boost::shared_ptr<T> inner;public:  Proxy(const boost::shared_ptr<T> &used) : inner(used) { assert(used); }  operator T& () { return *inner; }  operator const T&() const { return *inner; }};class DeviceBase { /* Base class Members */ };class DeviceFoo : public DeviceBase { /* Foo overrides */ };class DeviceBar : public DeviceBase { /* Bar overrides */ };typedef Proxy<DeviceBase> Device;// Create a foo devicedevices.push_back(Device(new DeviceFoo(/* Arguments */)));// Everything else happens as above[/quote]The proxy automatically cleans up behind you any memory you've allocated once it's not used anymore.
spudmuncher
spudmuncher
Thanks so much for all your advice.
I've decided to make std::vector my friend.
I think it is sufficient.
Although I am opting for the simple method mentioned right at the beginning, I'm glad for all I've learnt about the alternate techniques since.
Here is my final code if anyone is interested (it works).

#include <iostream>#include <vector> // needed for the list#include <algorithm> // needed for the find functionusing namespace std;// number of Example classes createdstatic unsigned int n_Example = 0;//Base for all auto-updted classesclass BaseUpdate{public:	BaseUpdate () {}	virtual ~BaseUpdate ();	virtual void Update () = 0; // must define this function in derived classes	void DeleteMe () { delete this; } // used internally, to delete the class use  delete Class;  instead};//Example classclass Example : public BaseUpdate{	int number; // just for demo purposespublic:	Example (int num)   { ++n_Example; number = num; } // all these are inline, but they don't have to be	~Example ()         { --n_Example; }	void Update ()      {cout << ": Updating Example" << endl; number++; }	int GetNumber ()    { return number; }};//List of auto-updted classestypedef vector <BaseUpdate *> List;List UpdateList;//Function for creating example classExample * CreateExample (int number){	cout << " An example has been created!" << endl;	Example * b = new Example (number);	UpdateList.push_back (b); // add to list	return b;}Example * CreateExample () // no params{	cout << " An example has been created!" << endl;	Example * b = new Example (0);	UpdateList.push_back (b);	return b;}//When the class is destroyedBaseUpdate:: ~BaseUpdate (){	cout << "An example has been deleted!" << endl;	List:: iterator i;	i = find (UpdateList.begin (), UpdateList.end (), this); // find the current class in the list	if (i != UpdateList.end ()) UpdateList.erase (i);}//Function for auto-uptding the classesvoid Run (){	//Update the classes	List:: iterator i; // think of this as if it were int i;  but you can get the value from the list by *(i)	for (i=UpdateList.begin (); i!=UpdateList.end (); i++)	{		BaseUpdate * b = *(i);		b->Update ();	}}//Function for clearing the update listvoid ClearAll (){	UpdateList.clear();}int main (){	Example *example1, *example2;	// Create a couple of thingys	example1 = CreateExample (5); // the number is an example of using a parameter	example2 = CreateExample (3);	// Record their numbers	cout << " example1 has this number value: " << example1->GetNumber () << endl;	cout << " example2 has this number value: " << example2->GetNumber () << endl;	// Automatically update (number++) all created Example classes	Run (); //This would normally be done in the main loop, but we don't have a main loop in this example	delete example1; //It's simple to delete stuff	example1 = CreateExample ();		// what are the new values	cout << " example1 has this number value: " << example1->GetNumber () << endl;	cout << " example2 has this number value: " << example2->GetNumber () << endl;		system ("PAUSE");		ClearAll ();	return 0;}

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.