need alittle help here please - simple

Post your questions, suggestions and experiences regarding game design, integration of external libraries here. For irrEdit, irrXML and irrKlang, see the
ambiera forums
Post Reply
Seven
Posts: 1034
Joined: Mon Nov 14, 2005 2:03 pm

need alittle help here please - simple

Post by Seven »

so i thought I had the polymorphism down, but apparently not. Can someone walk me through this example and explain why b::testing() is not called when the variable is destroyed?

Code: Select all

#include "stdafx.h"
#include "stdio.h"

class a
{
public :
	bool m_Dead;	

	a::a()			{ printf("a::a()\n");		init();						}
	virtual a::~a() { printf("a::~a()\n");		cleanup();					}

	virtual void init()		{	printf("a::init()\n");		m_Dead = false;	}
	virtual void cleanup()	{	printf("a::cleanup()\n");	testing();		}

	virtual void testing()	{	printf("a::testing()\n");					}
};

class b : public a
{
public :
	b::b()			{ printf("  b::b()\n");		init();						}
	virtual b::~b() { printf("  b::~b()\n");	cleanup();					}

	virtual void init()		{	printf("  b::init()\n");					}
	virtual void cleanup()	{	printf("  b::cleanup()\n");					}
	virtual void testing()	{	printf("  b::testing()\n");					}
};

#include <list>
using namespace std;
typedef std::list<a*> aList;
typedef aList::iterator aListIterator;

int _tmain(int argc, _TCHAR* argv[])
{
	aList m_List;

	b* testb = new b();
	testb->m_Dead = true;

	m_List.push_front(testb);

	aListIterator i;
	for (i = m_List.begin(); i != m_List.end(); )
	{
		if ( (*i)->m_Dead)
		{
			delete(*i);
			i = m_List.erase(i);
		}
		else 
		{
			++i;
		}
	}

	while(getchar()!='q');

	return 0;
}
Seven
Posts: 1034
Joined: Mon Nov 14, 2005 2:03 pm

Post by Seven »

I can make it happen by changing b class definition to this

Code: Select all

class b : public a
{
public :
	b::b()			{ printf("  b::b()\n");		init();						}
	virtual b::~b() { printf("  b::~b()\n");	cleanup();					}

	virtual void init()		{	printf("  b::init()\n");					}
	virtual void cleanup()	{	printf("  b::cleanup()\n");	a::cleanup();	}
	virtual void testing()	{	printf("  b::testing()\n");					}
};

but it sems that when a::cleanup() is called, it should call b::testing() instead of a::testing(). I apparently am wrong, but not sure why.
CuteAlien
Admin
Posts: 9734
Joined: Mon Mar 06, 2006 2:25 pm
Location: Tübingen, Germany
Contact:

Post by CuteAlien »

In short: Virtual functions don't work in constructors and destructors.

But there's a longer explanation of this from one of the guru's: http://www.artima.com/cppsource/nevercall.html

edit: Your second solution is rather clever. In the destructor of b the b-part of your object is still valid. Maybe the easiest way is to think that each constructor that get's called starts by setting his virtual functions and each destructor ends by removing his virtual functions. And construction is in order a->b while destruction is in order b->a.
IRC: #irrlicht on irc.libera.chat
Code snippet repository: https://github.com/mzeilfelder/irr-playground-micha
Free racer made with Irrlicht: http://www.irrgheist.com/hcraftsource.htm
Seven
Posts: 1034
Joined: Mon Nov 14, 2005 2:03 pm

Post by Seven »

sweet explanation. thanks for the info.
BlindSide
Admin
Posts: 2821
Joined: Thu Dec 08, 2005 9:09 am
Location: NZ!

Post by BlindSide »

It really has nothing to do with constructors/destructors I think.

It's because b::cleanup doesn't call testing, but a::cleanup does. When you call a function from a base class, all the functions that function calls will be from that class, not the inherited class.

If things didn't work this way think of all the trouble we would run into, a wouldn't get cleaned up properly!

PS: The article CuteAlien linked doesn't seem to be relevant because it refers to the dangers of calling pure virtual functions from constructors/destructors in the fear that the inherited object instance is not valid (Obviously it's not valid because the members are still being constructed, that's why you are in the base class's constructor in the first place!).
ShadowMapping for Irrlicht!: Get it here
Need help? Come on the IRC!: #irrlicht on irc://irc.freenode.net
Seven
Posts: 1034
Joined: Mon Nov 14, 2005 2:03 pm

Post by Seven »

BlindSide wrote:When you call a function from a base class, all the functions that function calls will be from that class, not the inherited class.

just making sure I understand this all. my little testbed is showing me that i really didnt understand before now.

in this code, calling a::testing() calls b::testing2() but not a::testing2()

Code: Select all

#include "stdafx.h"
#include "stdio.h"

class a
{
public :
	a::a()					{	printf("a::a()\n");							}
	virtual a::~a()			{	printf("a::~a()\n");						}
	virtual void testing()	{	printf("a::testing()\n"); testing2();		}
	virtual void testing2()	{	printf("a::testing2()\n");					}
};

class b : public a
{
public :
	b::b()					{	printf("  b::b()\n");						}
	virtual b::~b()			{	printf("  b::~b()\n");						}
	virtual void testing()	{	printf("  b::testing()\n");	a::testing();	}
	virtual void testing2()	{	printf("  b::testing2()\n");				}
};


int _tmain(int argc, _TCHAR* argv[])
{
	b* testb = new b(); 
	testb->testing();

	while(getchar()!='q');

	return 0;
}
however, if i move the call to the destructor, then the same call goes to a::testing2() instead...

Code: Select all

#include "stdafx.h"
#include "stdio.h"

class a
{
public :
	a::a()					{	printf("a::a()\n");							}
	virtual a::~a()			{	printf("a::~a()\n");	testing();			}
	virtual void testing()	{	printf("a::testing()\n"); testing2();		}
	virtual void testing2()	{	printf("a::testing2()\n");					}
};

class b : public a
{
public :
	b::b()					{	printf("  b::b()\n");						}
	virtual b::~b()			{	printf("  b::~b()\n");						}
	virtual void testing()	{	printf("  b::testing()\n");		}
	virtual void testing2()	{	printf("  b::testing2()\n");				}
};


int _tmain(int argc, _TCHAR* argv[])
{
	b* testb = new b(); 
	delete(testb);

	while(getchar()!='q');

	return 0;
}

vitek
Bug Slayer
Posts: 3919
Joined: Mon Jan 16, 2006 10:52 am
Location: Corvallis, OR

Post by vitek »

BlindSide wrote:When you call a function from a base class, all the functions that function calls will be from that class, not the inherited class.
You can most definitely call a derived class function from the base class. You can even call an abstract method from a base class method...

Code: Select all

#include <stdio.h>

class A
{
public:
  void method ()
  {
    method_ ();
  }

private:
  virtual void method_ () = 0;
};

class B : public A
{
private:
  virtual void method_ () { printf ("B::method_\n"); }
};

int main ()
{
  B b;
  b.method ();

  A* a = &b;
  a->method();

  return 0;
}
The issue is that a virtual function is being called from the constructor and destructor. This won't behave as most users will expect, so it is usually avoided. See C++ FAQ items 23.5 and 23.7.

The general rule is that the virtual function in the most derived class that is fully constructed will be called. If you call a virtual member function from a constructor or destructor, then the class being constructed or destructed is the most derived class.

Code: Select all

#include <stdio.h>

class A
{
public:
  A () { printf ("A::A()\n"); }
  A (int) { printf ("A::A(int)\n"); method (); }

  virtual void method ()
  {
    printf ("A::method()\n");
  }
};

class B : public A
{
public:
  B () { printf ("B::B()\n"); }
  B (int n) : A(n) { printf ("B::B(int)\n"); }

  virtual void method ()
  {
    printf ("B::method()\n");
  }
};

int main ()
{
  A a;
  a.method (); // most derived is A::method()

  B b;
  b.method (); // most derived is B::method()

  A* pa = &a;
  pa->method (); // most derived is A::method()

  A* pb = &b;
  pb->method (); // most derived is B::method()

  A aa (1); // most derived is A::method()

  // when B is constructed, the A subobject is constructed first.
  // when that object is constructed, it calls method(). at that point
  // in time, the B object is incomplete, so A::method is called.
  B bb (1); // most derived is A::method()
 
  return 0;
}
Travis
Last edited by vitek on Sun Nov 16, 2008 6:49 am, edited 1 time in total.
BlindSide
Admin
Posts: 2821
Joined: Thu Dec 08, 2005 9:09 am
Location: NZ!

Post by BlindSide »

Travis that is a pure virtual function, so it has no choice but to call the derived class. What if there was an implementation in A?
ShadowMapping for Irrlicht!: Get it here
Need help? Come on the IRC!: #irrlicht on irc://irc.freenode.net
vitek
Bug Slayer
Posts: 3919
Joined: Mon Jan 16, 2006 10:52 am
Location: Corvallis, OR

Post by vitek »

BlindSide wrote:Travis that is a pure virtual function, so it has no choice but to call the derived class. What if there was an implementation in A?
It would have no effect. The behavior would be exactly the same.

Code: Select all

#include <stdio.h> 

class A 
{ 
public: 
  void method () 
  { 
    method_ (); 
  } 

private: 
  virtual void method_ () { printf ("A::method_\n"); } 
}; 

class B : public A 
{ 
private: 
  virtual void method_ () { printf ("B::method_\n"); } 
}; 

int main () 
{ 
  B b; 
  b.method (); // prints B::method_

  A* a = &b; 
  a->method(); // prints B::method_

  return 0; 
} 
I was simply illustrating that a virtual function called from the base class calls the derived class implementation.

Travis
CuteAlien
Admin
Posts: 9734
Joined: Mon Mar 06, 2006 2:25 pm
Location: Tübingen, Germany
Contact:

Post by CuteAlien »

BlindSide wrote: PS: The article CuteAlien linked doesn't seem to be relevant because it refers to the dangers of calling pure virtual functions from constructors/destructors in the fear that the inherited object instance is not valid (Obviously it's not valid because the members are still being constructed, that's why you are in the base class's constructor in the first place!).
The article just mentions that with pure virtual functions the problem is more obvious (as it will crash) but it's not less dangerous with other virtual functions, as the problem might not even get noticed with them.

So I think my original answer is still OK.
IRC: #irrlicht on irc.libera.chat
Code snippet repository: https://github.com/mzeilfelder/irr-playground-micha
Free racer made with Irrlicht: http://www.irrgheist.com/hcraftsource.htm
Seven
Posts: 1034
Joined: Mon Nov 14, 2005 2:03 pm

Post by Seven »

it is amazing how long I have been doing this without realizing that it was wrong. In my implementation, the base class Cleanup() function runs through and calls many virtual functions.

virtual CSobject::~CSObject() { Cleanup(); }

virtual bool CSObject::Cleanup()
{
RemoveNode();
RemovePhysicsObject();
RemoveSound();
RemoveObjectGUI();
return false;
}


since all of these functions are virtual and redefined in the object classes, only the base class functions were being called. It worked great right up until it didnt work :)

my simple fix is to have the derived classes call the base class cleanup function as they are destroyed.

virtual CSobject_Fire::~CSObject_Fire() { Cleanup(); }
virtual bool CSObject_Fire::Cleanup() { return CSObject::Cleanup(); }


Works like a charm and i appreciate everyone's input on it. My other option is to call Cleanup() before deleting the object.

CSObject* obj = GetFactory()->GetObjectPointer(id);
if (obj)
{
obj->Cleanup();
delete(obj);
}


which also works fine. I just prefer the first.
CuteAlien
Admin
Posts: 9734
Joined: Mon Mar 06, 2006 2:25 pm
Location: Tübingen, Germany
Contact:

Post by CuteAlien »

Yeah, dangerous trap, I also didn't know about it for very long until something didn't work anymore :-)

Your solution works now, but beware that it will fail again if you derive once more. Do those functions actually have to be virtual? They sound pretty much like the stuff you use to clear arrays/lists and maybe delete some pointers in them. And that is probably all available in the baseclass. So maybe you can make them non-virtual and just add additional (also non-virtual) functions in your destructor if you need to cleanup additional stuff. Otherwise I would actually prefer the Cleanup as working around normal c++ functionality is rarely a good idea. Yet another solution might be to add a manager class which has some RemoveObject function. That function will know that cleanup needs to be called first and wraps the removal nicely so you don't have to remember in the rest of the code to call it.
IRC: #irrlicht on irc.libera.chat
Code snippet repository: https://github.com/mzeilfelder/irr-playground-micha
Free racer made with Irrlicht: http://www.irrgheist.com/hcraftsource.htm
BlindSide
Admin
Posts: 2821
Joined: Thu Dec 08, 2005 9:09 am
Location: NZ!

Post by BlindSide »

CuteAlien wrote:
BlindSide wrote: PS: The article CuteAlien linked doesn't seem to be relevant because it refers to the dangers of calling pure virtual functions from constructors/destructors in the fear that the inherited object instance is not valid (Obviously it's not valid because the members are still being constructed, that's why you are in the base class's constructor in the first place!).
The article just mentions that with pure virtual functions the problem is more obvious (as it will crash) but it's not less dangerous with other virtual functions, as the problem might not even get noticed with them.

So I think my original answer is still OK.
Ah, I see now, thanks CuteAlien and Vitek for clearing that up.
ShadowMapping for Irrlicht!: Get it here
Need help? Come on the IRC!: #irrlicht on irc://irc.freenode.net
Seven
Posts: 1034
Joined: Mon Nov 14, 2005 2:03 pm

Post by Seven »

CuteAlien wrote: Yet another solution might be to add a manager class which has some RemoveObject function. That function will know that cleanup needs to be called first and wraps the removal nicely so you don't have to remember in the rest of the code to call it.

agreed. this is the new code. it is called during the Level's PreFrame() call. This stops me from deleting anything during a render call, which happens during the Level's Frame() call.

Code: Select all

void ObjectList::KillDeadObjects()
{
	CSObjectListIterator i;
	for (i = m_List.begin(); i != m_List.end(); )
	{
		if ( (*i)->GetDead())
		{
			printf("deleting object - %s\n",(*i)->GetName());
			(*i)->Cleanup();
			delete(*i);
			i = m_List.erase(i);
		}
		else 
		{
			++i;
		}
	}
}
Dorth
Posts: 931
Joined: Sat May 26, 2007 11:03 pm

Post by Dorth »

Actually, the method I like best is:

Code: Select all

bool ObjectA::Cleanup(ObjectA*& Origin)
{
if (Origin == this)
{
//Cleanup here
delete this;
Origin = NULL;
return true;
}
return false;
}
Simple, fail safe, efficient ^^
Post Reply