Sign in to follow this  

[C++] Game objects referring to others

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

Good day, gamedev.net! I've come up with what seems to me an "ok" algorithm for game objects safely pointing to other game objects. I'm beginning to grasp the usefulness of the boost libraries, yet so far all I've used is boost::ptr_list. My question: Is there a simpler way to do this? Should I use a smart pointer? This is a very slimmed down version of what I'm trying to do, and may contain errors: The base class:
class CEntBase
{
public:
	enum status 
	{
		STATUS_OK = 0,
		STATUS_DESTRUCT_ME = 1
	};
	
protected:
	bool	m_bDestructMe;
	
public:
	virtual bool IsDestructing() { return m_bDestructMe; }

	virtual status Step( double &delta_time )
	{
		if ( m_bDestructMe )
			return STATUS_DESTRUCT_ME;
		return STATUS_OK;
	}
};






Game objects are stored in a boost::ptr_list<CEntBase> Game logic will loop through each entity in the list and if an entity returns STATUS_DESTRUCT_ME, the list will appropriately remove it from the list.
class CGlow : public CEntBase { /* ... */ };  // A glow object that appears randomly, moves around for a couple of seconds, and dies


class CBug : public CEntBase  // object that watches the glow
{
private:
	CGlow	*m_pgWatching;
	
public:
	/* [...] */

	status Step( double &delta_time )
	{
		if ( m_bDestructMe )
			return STATUS_DESTRUCT_ME;
		
		if ( m_pgWatching )
		{
			if ( m_pgWatching->IsDestructing() )
				m_pgWatching = NULL;
		}
		else
		{
			/* Find an existing glow to watch */
		}
		return STATUS_OK;
	}
	
};






Any feedback will be appreciated! EDIT: Had an epiphany; realized that smart pointers would be excellent in the case where an object being referenced and flagged for deletion is deleted from the game logic, the reference can still check if the object is deleted. [Edited by - _cdcel on January 7, 2008 6:46:15 PM]

Share this post


Link to post
Share on other sites
As for smart pointers, yea it's a good idea to use them anyways, saves a lot of headaches along the way. And as stated above weak_ptr would work. The object still gets released when there's no references left (shared_ptr) and you can check whether the object still exists using weak_ptrs expired method.

EDIT: Seems that you already figured it out [smile].

Share this post


Link to post
Share on other sites
Thanks for the replies!

After spending much of yesterday trying to work with lists and smart pointers, I've finally got a it to work:


class CEntBase
{
protected:
bool m_bDestructed;

public:
enum Status
{
STATUS_CONTINUE = 0,
STATUS_DELETE = 1
};

std::auto_ptr<CEntBase> *m_refptr;

virtual bool IsDead() { return m_bDestructed; }
virtual void Kill() { m_bDestructed = true; }

virtual Status Step( const double &delta_time ) { return m_bDestructed ? STATUS_DELETE : STATUS_CONTINUE; }
virtual void Draw() { }
};


class CGame
{
private:
std::list< std::auto_ptr<CEntBase>* > m_listObjects; // The one and only list of game objects.

public:
~CGame()
{
// Make a clean destruction.
for ( std::list<std::auto_ptr<CEntBase>*>::iterator itr = m_listObjects.begin(); itr != m_listObjects.end(); ++itr )
delete (*itr);
}

CEntBase::Status Step( const double &delta_time )
{
// Give each entity a turn to step:
std::list<std::auto_ptr<CEntBase>*>::iterator itr_end = m_listObjects.end();
for ( std::list<std::auto_ptr<CEntBase>*>::iterator itr = m_listObjects.begin(); itr != itr_end; /* no increment */ )
{
if ( (*itr)->get()->Step( dt ) == CEntBase::STATUS_DELETE )
{
// thelist->erase() doesn't seem to delete (*itr) properly:
std::auto_ptr<foobar> *tmp = *itr;
itr = thelist->erase( itr );
delete tmp;
}
else
++itr;
}

return CEntBase::STATUS_CONTINUE;
}

void AddObject( CEntBase *pObject )
{
// Give the object its auto_pointer so that other objects can use it to make references to it.
std::auto_ptr<CEntBase> *pRef;
m_listObjects.push_back( pRef = new std::auto_ptr<CEntBase>( pObject ) );
pObject->m_refptr = pRef;
}
}





(Should use typedefs to keep things consistent and cleaner)

I've made some vigorous tests, and it seems to work ok for me. I'm not sure if I should give *every* entity the ability to make references of itself (particles and other simple entities which probably won't need to be referenced).


EDIT:
The only "iffy" part I have about my code is this section:


// thelist->erase() doesn't seem to delete (*itr) properly:
std::auto_ptr<foobar> *tmp = *itr;
itr = thelist->erase( itr );
delete tmp;




Is there anything wrong with it?

Share this post


Link to post
Share on other sites
I'm not going to go into too much detail here, but a couple of quick comments:

1. Make sure you understand the ownership semantics of std::auto_ptr, and then take another look at whether it's advisable to use them in the way you're using them. Also, take a look at the aforementioned Boost smart pointers.

2. Ask yourself the question, "Why am I allocating smart pointer objects dynamically and then storing them by reference?" Then, if you have a good answer, come back and explain it to us :-) (I'll venture a guess that you're trying to work around auto_ptr's ownership semantics, in which case see item 1, above.)

3. Why does an object need a reference to itself?

Note that by 'reference' here I'm referring to the concept of a reference, not to C++ reference types.

Share this post


Link to post
Share on other sites
The truth is, I don't have a good answer. :)
But after coming back to the code after a few days, letting the logic settle in, I've come up with this new draft:


/* [...] */

using namespace std;
using namespace boost;

class foobar
{
private:
static int count;
int id;

public:
foobar() { printf( "Construct %i\n", id = ++count ); }
~foobar() { printf( "Destruct %i\n", id ); }

void test() { printf( "Test %i\n", id ); }
};

int foobar::count = 0;

void test1()
{
// Set memory leak detection:
_CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );

ptr_list< shared_ptr<foobar> > *thelist;
shared_ptr<foobar> *q;
foobar *ptr;

thelist = new ptr_list< shared_ptr<foobar> >;
thelist->push_back( q = new shared_ptr<foobar>( ptr = new foobar ) );

/* shared_ptr<foobar> r( ptr ); */ // incorrect, causes dual-ownership of the pointer
shared_ptr<foobar> r( *q );

delete thelist;

r.get()->test();


system("PAUSE");
}









Quote:
Original post by jyk
3. Why does an object need a reference to itself?


So other objects can increment the reference count (of the smart pointer) when referencing the object. :)

Thanks for the reply, jyk, I've learned a lot of smart pointers!

[Edited by - _cdcel on January 10, 2008 1:12:34 AM]

Share this post


Link to post
Share on other sites
Quote:
So other objects can increment the reference count when referencing the object. :)


The smart pointers should handle that automatically. If you have to do this yourself, you're using them incorrectly.

Share this post


Link to post
Share on other sites
Quote:
Original post by _cdcel
*** Source Snippet Removed ***
Looks like there is still some confusion.

A good rule of thumb for dynamic allocations is, every time you create an object via new, ask yourself, "Why am I creating this object dynamically rather than just declaring an object of that type directly?" If you don't have a good answer, then don't create the object dynamically. (Note that in your posted example this applies both to the ptr_vector object and shared_ptr objects.)

Now, I believe you're misusing ptr_vector here.

The Boost pointer container and smart pointer classes are intended to address similar problem domains, that is, ownership management and automatic cleanup of dynamically allocated resources. Typically you would use one or the other, but not both. I've never seen ptr_vector and shared_ptr used together like that, and off the top of my head I really can't think of any reason for doing so. Just store the smart pointer objects by value in a 'regular' container such as std::vector.

As for objects referencing other objects, I would use either boost::shared_ptr or boost::weak_ptr depending on whether object A shares ownership of object B, or simply 'knows about' object B.

Here is a quick example illustrating some of these concepts (not compiled or tested):

#include <iostream>
#include <vector>

#include <boost/shared_ptr.hpp>
#include <boost/weak_ptr.hpp>

// Notice no using directives here - it's good to get out of the habit of
// using these, as it clutters the global namespace, and (IMO) can make
// code more difficult to read.

// Assume this is a base class that will be used as part of a class hierarchy:
struct Foo {
virtual ~Foo() {}

void Hello() const { std::cout << "Hello from a Foo!" << std::endl; }
};

typedef boost::shared_ptr<Foo> FooSharedPtr;
typedef boost::weak_ptr<Foo> FooWeakPtr;
typedef std::vector<FooSharedPtr> Foos;

int main()
{
// A container of Foo's (no need to create this dynamically):
Foos foos;

// Add a couple of Foo's:
foos.push_back(FooSharedPtr(new Foo));
foos.push_back(FooSharedPtr(new Foo));

// Grab the last one (here we are sharing ownership):
FooSharedPtr foo_shared = foos.back();

// Ditto, but this time it's a 'knows a' relationship:
FooWeakPtr foo_weak = foos.back();

// Clear the container. Note that the first Foo in the container will
// be deleted here, but the second will not, since another shared
// pointer still references it (you can easily add debug output to
// confirm this):
foos.clear();

// Grab the remaining Foo from the weak pointer (since the referenced
// Foo still exists, lock() will return a valid shared pointer - see
// the documentation for details):
FooSharedPtr locked_foo = foo_weak.lock();
assert(locked_foo);
locked_foo->Hello();

// Now, reset the remaining Foo shared pointer, which will in turn
// delete the remaining Foo:
foo_shared.reset();

// Now lock() fails:
locked_foo = foo_weak.lock();
assert(!locked_foo);

// Note that if there were any remaining Foo references at this point
// (either 'free' or in the Foo container), they would be destroyed at
// this point and the corresponding dynamically allocated Foo objects
// deleted.
}

Share this post


Link to post
Share on other sites

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