List of subclass pointers...

The Partridge Family were neither partridges nor a family. Discuss.
binbinhfr
Posts: 78
Joined: May 9th, 2019, 10:57 pm

List of subclass pointers...

Post by binbinhfr » March 28th, 2020, 10:40 am

Hi there,

a little question.

I have
class A;
class B0 : A
class B1 : A
class B2 : A

std::list<A*> listPA;

in listPA, I store pointers on B0, B1 or B2 objects.

but when I parse listPA, I retrieve A* items.

then I have a function f() that I want to call on these A* that I retrieve from the list,
but I would like to decline f() into separate behaviour, overwriting f() into f(B1*) , f(B2*), f(A*)
note that f(A*) should be the default behaviour (used on B0 objects for example), but f(A*) should be overwritten by the two others for B1* and b2* objects...

Does it make sense into C++ ?
in other terms, does an A* pointer "knows" that he hides a B1* for example ?

albinopapa
Posts: 4373
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Re: List of subclass pointers...

Post by albinopapa » March 28th, 2020, 4:13 pm

Code: Select all

class A
{
public:
     virtual void F( A* obj );
};

class B0 : public A
{
public:
     // Doesn't need to define F() if A::F() handles behavior
};

class B1 : public A
{
public:
     void F( A* obj ) override;
};

class B2 : public A
{
public:
     void F( A* obj ) override;
};
Calling objB1->F( objA ) will call B1::F( A* ), but to determine what the underlying type of A* is, you'll need to use either runtime type info ( typeid and std::type_info ) or use some other means. One such method is to use a form of double dispatch. Have F() be the public interface that calls some overloaded function with the real static type as the parameter.

Code: Select all

class B0;
class B1;
class B2;
class A {
public:
	void f( A* pObj ) { pObj->visit(); }

protected:
	virtual void visit() = 0;
	void DoWork( A*, A* ) {}
	void DoWork( B0* ){ /* Do whatever f(A*) was going to do with A* now knowing it's a B0* */}
	void DoWork( B1* ){ /* Do whatever f(A*) was going to do with A* now knowing it's a B1* */}
	void DoWork( B2* ){ /* Do whatever f(A*) was going to do with A* now knowing it's a B2* */}
};

class B0 :public A {
public:

private:
	void visit( )override { DoWork( this ); }
};

class B1 :public A {
public:

private:
	void visit()override { DoWork( this ); }
};

class B2 :public A {
public:

private:
	void visit()override { DoWork( this ); }
};
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

albinopapa
Posts: 4373
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Re: List of subclass pointers...

Post by albinopapa » March 28th, 2020, 4:21 pm

Here's the explanation of what the second section of code does.

Calling pObj->F( pObj2 ) will then call the virtual function visit() on whatever type pObj2 is. So if pObj2 is a B0*, then B0::visit() is called and if it's a B1* then B1::visit() is called. Once inside the visit() function, the base DoWork() function is called using the type of 'this' pointer to dispatch to the correct overload of the function. If you are also looking to have B1 and B2 handle A*, B0*, B1* and B2* objects, you'll need to do a bit more work in making overloads that eventually resolve both pointers. This just assumes that A is the class that handles the behavior while B0-B2 are basically just data storage with different types of data. So if this is for the parser, then this method would allow you to tell if it's a Statement* or Expression* or something like that while class A still does all the actual parsing.
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

binbinhfr
Posts: 78
Joined: May 9th, 2019, 10:57 pm

Re: List of subclass pointers...

Post by binbinhfr » March 28th, 2020, 9:55 pm

Thanks man. This is clever.
It's not for the parser (which is quite finished), but for the game data.
The problem I have with your solution is :
B1 and B2 will have some specific unique data that I do not want to put into A, and each Doworks located in A cannot "see" these data and do the specific work I want to do on B1 or B2... Or am I wrong ?

albinopapa
Posts: 4373
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Re: List of subclass pointers...

Post by albinopapa » March 29th, 2020, 12:57 am

No, you are correct, A will not be able to access B1::( data ) nor B2::( data ) unless you create accessors ( get/set that you loathe so much ). Ah, the joys of OOP and encapsulation.
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

albinopapa
Posts: 4373
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Re: List of subclass pointers...

Post by albinopapa » March 29th, 2020, 12:59 am

That being said though, that's only if the data is private...but if you aren't setting things to private then once you get into the DoWork function with the actual B1/B2 type, then you will have access to their public data members since they are B1s and B2s and not just a polymorphic A*.
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

albinopapa
Posts: 4373
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Re: List of subclass pointers...

Post by albinopapa » March 29th, 2020, 4:47 am

Code: Select all

class B0;
class B1;
class B2;
class A {
public:
	void f( A* pObj ) { pObj->visit(); }

protected:
	virtual void visit() = 0;
	void DoWork( A*, A* ) {}
	void DoWork( B0* ){ /* Do whatever f(A*) was going to do with A* */}
	void DoWork( B1* pObj ) { if(pObj->Lives()<=0)/* kill object */; }
	void DoWork( B2* pObj ) { if( pObj->Name() == "Anon" ) pObj->Name( "George" ); }
};

class B0 :public A {
public:

private:
	void visit( )override { DoWork( this ); }
};

class B1 :public A {
public:
	int Lives()const { return lives; }
private:
	void visit()override { DoWork( this ); }

	int lives = 0;
};

class B2 :public A {
public:
	std::string const& Name()const { return name; }
	void Name( std::string name_ ) { name = std::move( name_ ); }
private:
	void visit()override { DoWork( this ); }

	std::string name = "Anon";
};
This shows using functions specific to B1 and B2 that an object of A* wouldn't have access to.
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

binbinhfr
Posts: 78
Joined: May 9th, 2019, 10:57 pm

Re: List of subclass pointers...

Post by binbinhfr » March 29th, 2020, 7:57 am

Ok. I'm a little disappointed that the DoWork of B2 cannot appear somehow in B2 member (also in B2.cpp file, more convenient and lisible).

Another question (let's go back to the list stuff) :
Let say I would like external functions (not a class member of A, B1, B2, etc...), called FindB1 (or FindB2, etc...) that would retrieve the first B1* (or B2*, etc...) of the list. How would you do this ?

B1* FindB1();
B2* FindB2();

To clear out what I'm doing : class A would be the main class of any "movable" object on my game map. (with general member and methods like x, y, Move, Destroy), and B1, B2 would be "specific object", like "robot", "key", "rock", with more specific and unique members and methods.
For example, I want lists of A* for inventories that can store any type of A.
The map would also be a vector of A* of size width*height (each position would contain either nullptr or a A*) -> convenient to refresh display and test collision, or find nearby objects.

PS: well, the simplest way to achieve what I want seems to be the addition of a "type" member to the A class, remembering the "real" type of object behind. (note that the A class is pure virtual somehow, because I never instantiate A objects).

albinopapa
Posts: 4373
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Re: List of subclass pointers...

Post by albinopapa » March 29th, 2020, 10:06 am

Hehe, yeah, having an enum or something is one way of determining the type as well.

To use the visitor pattern ( the one I used in previous post ) you need to visit the class to get it's type. This makes the compiler look up the function pointer in the vtable then calls that function. Once in the member function, the type is known. From there, you would call an overloaded function with the known type.

To determine if two types are equal takes some acrobatics.

You have to visit one object to get the known ( discrete ) type.
Then call into a function from the discrete type member function passing in both the discrete type and the abstract type objects.
Then you must do the same for the unknown type, but this time passing in the discrete type as the parameter to a set of overloaded functions taking in the discrete type object.
Now that you have both types resolved, you can call into one of the overloaded functions that takes in both discrete type objects.

Code: Select all

// Forward declarations
class A;
class B0;
class B1;
template<typename T> bool IsSame( T* left, A* right );

class A
{
public:
	virtual bool IsSame( A* ) = 0;

protected:
	template<typename T>
	friend bool IsSame( T* left, A* right );
	virtual bool IsSame_( B0* ) = 0;
	virtual bool IsSame_( B1* ) = 0;
};

class B0 : public A
{
public:
	bool IsSame( A* other )override { return ::IsSame( this, other ); }

private:
	bool IsSame_( B0* ) override { return true; }
	bool IsSame_( B1* ) override { return false; }
};

class B1 : public A
{
public:
	bool IsSame( A* other ) override { return ::IsSame( this, other ); }

private:
	bool IsSame_( B0* )override { return false; }
	bool IsSame_( B1* )override { return true; }
};

template<typename T>
bool IsSame( T* left, A* right ) { return right->IsSame_( left ); }

// Test usage
B0 b0;
B1 b1;
B1 b2;
A* as[] = { &b0, &b1, &b2 };

// This part you can put in your find function
B1* pb1 = nullptr;
if( as[ 1 ]->IsSame( as[ 2 ] ) )
	pb1 = reinterpret_cast< B1* >( as[ 0 ] );
It's a little long winded, but it works. This isn't probably something you'd want to do if you have lots of types to dispatch to that inherit from A, would take a while.
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

binbinhfr
Posts: 78
Joined: May 9th, 2019, 10:57 pm

Re: List of subclass pointers...

Post by binbinhfr » March 29th, 2020, 11:31 am

Waooo, C++ does not stop to surprise me. Writing a C++ compiler must be a challenge !!!
But as you guessed, I will have a lot of classes derivated from my "movable object" class, so I choose to implement the enum type in the main class. This type is automatically fixed by the subclass constructor. It works like a charm.

Post Reply