Sign in to follow this  

[Solved] Clean memory allocation

This topic is 3186 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

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]

Share this post


Link to post
Share on other sites
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?

Share this post


Link to post
Share on other sites
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 class
class 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[i] != NULL) pointer[i]->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 destroyed
virtual CRefObject:: ~CRefObject ()
{
container.remove (this);
}


// a couple of derived devices
class 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 device
CDeviceA * 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 this
void run ()
{
CDeviceA * device;

device = CreateDeviceA ();
container.updateAll ();

delete device;
}




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

Share this post


Link to post
Share on other sites
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]

Share this post


Link to post
Share on other sites
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

Share this post


Link to post
Share on other sites
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 interface
class 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 list
void AddForUpdate( DeviceBase *pDevice )
{
updateList.push_back( pDevice );
}
//----------------------------------------------------------------------------
// Flag it for later removal.
// Don't outright remove it, incase this is called durring update
void FlagForRemove( DeviceBase *pDevice )
{
removeList.push_back( pDevice );
}
//----------------------------------------------------------------------------
// Check if it is in the remove list
bool 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 list
void 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 loop
void 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 device
class 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]

Share this post


Link to post
Share on other sites
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

Share this post


Link to post
Share on other sites
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 DeviceBase
class 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<MyClass> imp;
Is that some sort of keyword.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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<foo> 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.

Share this post


Link to post
Share on other sites
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 somewhere

void 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".

Share this post


Link to post
Share on other sites
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 removed
public:
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 list
void MemManager:: AddForUpdate ( DeviceBase * p_device )
{
updateList.push_back( p_device );
}
//----------------------------------------------------------------------------
// Flag for later removal.
// Don't outright remove it, incase this is called during update
void MemManager:: FlagForRemove ( DeviceBase * p_device )
{
p_device->SetIsDead (true);
removeList.push_back( p_device );
}
//----------------------------------------------------------------------------
// Check if it is in the remove list
bool 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 list
void 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 loop
void 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 class
class 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 class
class 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 Func
void 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]

Share this post


Link to post
Share on other sites
If you have a pointer to an object that you DO NOT own the lifetime of, you have a boost::weak_ptr<T>.

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

boost::weak_ptr<T> must be created from a boost::shared_ptr<T>.

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

When they want to access the FooClass, they create a temporary boost::shared_ptr<FooClass> 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 <boost/shared_ptr.hpp>
#include <boost/weak_ptr.hpp>

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<T>() (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.

Share this post


Link to post
Share on other sites
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 :) ).

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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 it
devices.push_front(Device(/* constructor arguments */));
DeviceHandle handle = devices.begin();

// Have some fun with the device
handle -> frobnicate();

// Remove it from the list
devices.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 devices
devices.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 device
devices.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.

Share this post


Link to post
Share on other sites
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 function

using namespace std;

// number of Example classes created
static unsigned int n_Example = 0;

//Base for all auto-updted classes
class 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 class
class Example : public BaseUpdate
{
int number; // just for demo purposes
public:
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 classes
typedef vector <BaseUpdate *> List;
List UpdateList;

//Function for creating example class
Example * 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 destroyed
BaseUpdate:: ~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 classes
void 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 list
void 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;
}

Share this post


Link to post
Share on other sites
True.
I've been trying to figure it out.
Whenever I delete an element, I get an error.
Nothing I do seems to help.

---------------------------
Microsoft Visual C++ Debug Library
---------------------------
Debug Assertion Failed!

Program: ...rojects\Vector Class Exercise\Debug\Vector Class Exercise.exe
File: d:\program files\microsoft visual studio 9.0\vc\include\vector
Line: 116

Expression: ("this->_Has_container()", 0)

For information on how your program can cause an assertion
failure, see the Visual C++ documentation on asserts.

(Press Retry to debug the application)
---------------------------
Abort Retry Ignore
---------------------------


void ClearAll ()
{
List:: iterator i;
for (i=UpdateList.begin (); i!=UpdateList.end (); i++)
{
BaseUpdate * b = *(i);
delete b; // or b->DeleteMe(); The error is the same either way.
}

UpdateList.clear();
}


Share this post


Link to post
Share on other sites
I should think first then post.
Figured it out.

I didn't realize that i increments automatically when calling erase.


//Function for clearing the update list
void ClearAll ()
{
List:: iterator i;
i = UpdateList.begin ();
while (i!=UpdateList.end ())
{
BaseUpdate * b = (*i);
i = UpdateList.erase (i); // increments i automatically
b->DeleteMe ();
}

UpdateList.clear();
}

Share this post


Link to post
Share on other sites
Quote:
Original post by spudmuncher
I didn't realize that i increments automatically when calling erase.


The return value from erase() is an iterator to the next valid item in the container. This is done so that you can keep going with the iteration. When you erase an element, the iterator that referred to it is invalidated. Unusable. You cannot reliably dereference it, nor increment or decrement it. The iterator cannot be thought of in terms of storing an index into the list, and retrieving the index'th element on demand. It just doesn't work like that. It stores some unknown sort of information that allows it to get at an element, and traverse the container forward (and usually backward).

Share this post


Link to post
Share on other sites
Quote:
Original post by spudmuncher
b->DeleteMe ();

Why create a DeleteMe() function, while 'delete b;' does exactly the same - and also shows what really happens, rather than hiding it for the programmer? 'DeleteMe()' sounds like some sort of clean-up function, and while the name does signal something along the way of deleting the actual object, it's more confusing than a simple 'delete b;' statement. Things like this make maintenance a bit more trickier imho.

Share this post


Link to post
Share on other sites

This topic is 3186 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