almost identical derived class function signatures

This problem has come up for me in slightly more complicated form, I have created a contrived example to shorten the code.

For a given base class, if all derived classes will need to define a particular function I can use pure virtual. But what if all the derived classes will have slightly different footprints? Do I have to define every pure virtual in the base, and then define (other than the single useful derived function) a bunch of dummy derived functions in each derived class?

Is there some way to say: all derived classes must define this base function but they might have slightly different argument lists?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <iostream>
using namespace std;

class numberBase {
public:
	virtual void oneHalf(int& intArg) = 0;
	virtual void oneHalf(double& dubArg) = 0;
};

class integerDerived : public numberBase {
public:
	void oneHalf(int& intArg) { intArg /= 2; }
	void oneHalf(double& dubArg) {};
};

class doubleDerived : public numberBase {
public:
	void oneHalf(int& intArg) {};
	void oneHalf(double& dArg) { dArg /= 2; }
};

int main() {
	int anyInt = 1024;
	double anyDouble = 1024.1024;
	integerDerived iDeriv;
	doubleDerived dDeriv;

	while (anyInt >= 1 ) {
		printf("%d   %f  \n", anyInt, anyDouble);
		iDeriv.oneHalf(anyInt);
		dDeriv.oneHalf(anyDouble);	}
}
My first thought was "templates", but they can't really work with inheritance like your example appears to require.

The problem with making pure virutal functions is that every derived class would need to have instances of those functions. If the functions have varying types, then yeah, you're pretty much hosed.

One solution might be to not have pure virtual functions in the base, but to simply downcast to the derived class and then call its member function. That's the easiest and most straightforward.

The only other way I can think to accomplish this would be with the dreaded and extremely type-unsafe variable argument solution (eplisis). ie:

 
void oneHalf(...);


But ugh. I wouldn't fall back to that unless there was really no other way (I'd even go the "make a bunch of dummy functions" route before that)

More likely -- this might be solved by changing the way your program is designed. Maybe you can give us the more complicated scenario and we'll see if we can't come up with a better way to approach it where this isn't necessary.
Thanks Disch for the feedback. The slightly more complicated problem was actually not coded yet but just some design ideas I was thinking about for a game program where different types of characters all need to swim, fight, run, etc... but the argument lists would be slightly different based on the character, his equipment, and his environment. Some of the attributes of course would just be class members, but some would change externally and don't really make sense inside the class.

1
2
3
4
5
6
7
//all fighters must be able to run to a location
virtual fighter::runto(x,y,z) = 0;

while(x < LIMIT) {
  landFighter.runto(x, y, z); //always on hard ground or on pathway above mud
  swampFighter.runto(x, y, z, mudthickness++); //always in the mud
}


Well rather than having equipment/environment change the interface of the character, I would try to have the character interface generic, and have it interact with the environment underneath. For example:

1
2
3
4
5
6
7
8
9
10
11
12
landFighter.runto(x,y,z);
swampFighter.runto(x,y,z);  // same interface

LandFighter::runto( /*xyz*/ )
{
  if(mud_on_path)  CantMove();
}

SwampFighter::runto( /*xyz*/ )
{
  if(mud_on_path)  CheckThicknessAndStuff();
}


Changing the way your program interfaces with the character for each set of circumstances is troublesome because
1) it requires you to hardcode each circumstance into the character
2) it requires if/else chains to determine which interface to use. if(fighter.equipment == thisitem) DoThis(); else if(blah) DoThat(); Blech.

My suggestion would be to make each different kind of circumstance a class (each circumstance class derived from a common abstract base class), and give the characters pointers to those circumstances. It's kind of hard to explain -- I hope I'm making sense -- it all makes sense in my head I swear! XD

Anyway the trick is to get the interface to be a basic as possible, and put the specifics in derived classes. That way as the specifics change, you can just change (or add) one circumstance's handler, rather than having to go into each and every character's class and add a new else if to the chain.

As for inheritance -- It basically comes down to a "is a" relationship. A fighter "is a" character... a landfighter "is a" fighter, etc. However this "is a" relationship has a condition that for a fighter to really be a character, it needs to do everything that a character can do. IE: the outward interface has to be the same. If you really can't make it the same, you either should redesign the interface, or reconsider whether or not it really has a "is a" relationship. A swampfighter might not be a fighter, for example, if he can't do things that fighters can normally do -- but he might be a character! It's more about how the class behaves.

As for program flow, it's up in the air how you want to approach it because it can vary depending on all the details. But my first instinct would be to have.... how to word this.... like.... if logic A is dependent on logic B, then have logic A's code call logic B's. Don't do if(logicB) DoLogicA1(); else DoLogicA2();

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
 // bad
void MoveCharacters()
{
  if(acharacter->InASwamp())
    acharacter->MoveThroughSwamp(a,b,swamp->GetThickness());
  else
    acharacter->MoveOnLand(a,b);
}

 // better
void MoveCharacters()
{
  acharacter->Move(a,b);
}

void Character::Move(int a,int b)
{
  if(InASwamp())
    MoveThroughSwamp(a,b,swamp->GetThickness());
  else
    MoveOnLand(a,b);
}


This way the dependency stays where it matters, and the overall game flow can be clueless about it. If each character checks their own depencies, they can respond to them in their own way. If a swampfighter can move through the swap and on land, for example, it might not even have to do that InASwamp() check.

Anyway I don't know if I'm making much sense. Design is tricky though. I've found that's it's actually the hardest part of programming -- but once you have a good design, the programming part is soooooo easy.
-- but once you have a good design, the programming part is soooooo easy.

Funny you should say that, I just got back from my evening class and our professor's lecture tonight was about design and he said the same exact same thing in the opposite way "if you don't start with a good design, it doesn't matter how good the programmer is".

I think I understand what you're saying, it's better to keep the interface (argument lists) consistent and if an extra argument is needed it can be retrieved by the class that needs it as in your last (//better) example using InASwamp() and getThickness() functions particular to the class that needs them

Well thank you Disch for your ideas and input, they are much appreciated.
Topic archived. No new replies allowed.