a problem with poliporphism

N

Nafai

Hello. I'll try to explain my problem with an example:

I have the following classes:

class A {
public:
string name;
....
};

class B : public A
{
B(int x) {b=x; name="I am B"; }
int b;
getB() { return b };
};

class C : public A
{
C(int x) {c=x; name="I am C"; }
int c;
getC() { return c; }
};

Now, I need a vector<A*> v;

An there is a function like the following:

void p()
{
...
if(v->name=="I am B"){ v->getB() ...};
...
if(v[j]->name=="I am C") { v[j]->getC() ... };
...
};

It doesn't work because v contains objects of class A, but A doesn't
implement getB() nor getC().
But I need to have in the same vector objets of A, B and C. And I don't want
to declare getB and getC as virtual in A, because these methods will only be
defined in classes B and C respectively.

How can I do that?

Thanks.
 
N

Nafai

Some corrections:
I have the following classes:

class A {
public:
string name;
...
};

class B : public A
{
int b;
public:
B(int x) {b=x; name="I am B"; }
getB() { return b };
};

class C : public A
{
int c;
public:
C(int x) {c=x; name="I am C"; }
getC() { return c; }
};

Now, I need a vector<A*> v;

An there is a function like the following:

void p()
{
...
if(v->name=="I am B"){ v->getB() ...};
...
if(v[j]->name=="I am C") { v[j]->getC() ... };
...
};

It doesn't work because v contains objects of class A, but A doesn't
implement getB() nor getC().
But I need to have in the same vector objets of A, B and C. And I don't
want to declare getB and getC as virtual in A, because these methods will
only be defined in classes B and C respectively.

How can I do that?

Thanks.
 
V

Victor Bazarov

Nafai said:
Some corrections:

More corrections
public:
B(int x) {b=x; name="I am B"; }

int getB() { return b };
public:
C(int x) {c=x; name="I am C"; }
getC() { return c; }

int getC() { return c; }
};

Now, I need a vector<A*> v;

An there is a function like the following:

void p()
{
...
if(v->name=="I am B"){ v->getB() ...};
...
if(v[j]->name=="I am C") { v[j]->getC() ... };
...
};

It doesn't work because v contains objects of class A,


Actually, v contains _pointers_ to objects of class A. It would be
a disaster if v contained _objects_.

Declare

virtual int getValue() = 0;

in A and make B and C implement it by calling getB and getC respectively.

V
 
A

Alexandros

Declare

virtual int getValue() = 0;

in A and make B and C implement it by calling getB and getC respectively.

V

what about if C is like this (A and B are like before):

class C {
int c1, c2;
public:
int getC1() { return c1; }
int getC2() { return c2; }
};

vector<A*> v;
void p()
{
....
v->getC1();
...
v->getC2();
...
}



And another question:

list<A*> l;
A* p = new C(...);
l.push_front(p);
l.pop_front();

is *p destroyed?

Or even more: will list<A*>'s destructor destroy all the elements of l ?
 
V

Victor Bazarov

Alexandros said:
Declare

virtual int getValue() = 0;

in A and make B and C implement it by calling getB and getC respectively.

V


what about if C is like this (A and B are like before):

class C {
int c1, c2;
public:
int getC1() { return c1; }
int getC2() { return c2; }
};

vector<A*> v;
void p()
{
....
v->getC1();
...
v->getC2();
...
}


Not going to work. If C and B are so different, they have no business to
reside in the same container to begin with. The only reason for B and C
to have a base class is if they have something _in common_. If they have
_nothing_ in common, what polymorphism can we talk about here?

Of course, you could try to claim that they have the _name_ in common, but
come on, what polymorphism is there if the only thing they share is the
ability to have a name? If that's so, the only thing you can do with them
polymorphically is to learn the names of the objects.
And another question:

list<A*> l;
A* p = new C(...);
l.push_front(p);
l.pop_front();

is *p destroyed?
No.

Or even more: will list<A*>'s destructor destroy all the elements of l ?

No, it will not. It has to be done manually (since it was allocated
without 'list's involvement, the list has no business trying to free
them.

V
 
N

Nafai

Not going to work. If C and B are so different, they have no business to
reside in the same container to begin with. The only reason for B and C
to have a base class is if they have something _in common_. If they have
_nothing_ in common, what polymorphism can we talk about here?

Of course, you could try to claim that they have the _name_ in common, but
come on, what polymorphism is there if the only thing they share is the
ability to have a name? If that's so, the only thing you can do with
them polymorphically is to learn the names of the objects.

That was only an example; classes A,B and C have many things in common.
Actually their only difference is what I show in that example.
 
V

Victor Bazarov

Nafai said:
That was only an example; classes A,B and C have many things in common.
Actually their only difference is what I show in that example.

Well, then it's not fair to talk about them out of context...

The whole idea of using containers with polymorphic types is that the
objects (through pointers) are used polymorphically. As soon as you have
to figure out the "real" types of objects via some kind of RTTI (your 'if
name is "I am B" ' _is_ RTTI, in essence), you step away from polymorphic
behaviour.

Yes, you can always do

if (v->is_really_of_type_B()) { // whatever 'is_really_of_type_B' is
B* pb = dynamic_cast<B*>(v);
// use pb however you want
}
else if (v->is_really_of_type_C()) { // same here
C* pc = dynamic_cast<C*>(v);

... et cetera ...

Hell, you can write

if (B* pb = dynamic_cast<B*>(v)) {
// use pb
}
else if (C* pc = dynamic_cast<C*>(v)) {
// use pc
}

and so on. But both approaches _require_ that you know what types derive
from 'A' _ahead_of_time_, while writing that code. What if I come later
and derive another type from A and want to store it in the same container?
SOL? Or do I have to come in and edit that function and every piece of
code that does that?

While it is possible to circumvent polymorphism in C++, you should
definitely think twice before writing code like that. Write some kind of
special processing code in the base class and make every derived class
override that thus providing _polymorphic_ processing.

You either put effort initially to _keep_ polymorphism, to adhere to OOD
principles, or you don't, then somebody else or maybe you will have to
put some effort _later_ to keep the system working.

V
 
J

jeffc

Nafai said:
Hello. I'll try to explain my problem with an example:

I have the following classes:

class A {
public:
string name;
...
};

class B : public A
{
B(int x) {b=x; name="I am B"; }
int b;
getB() { return b };
};

class C : public A
{
C(int x) {c=x; name="I am C"; }
int c;
getC() { return c; }
};

Now, I need a vector<A*> v;

An there is a function like the following:

void p()
{
...
if(v->name=="I am B"){ v->getB() ...};
...
if(v[j]->name=="I am C") { v[j]->getC() ... };
...
};

It doesn't work because v contains objects of class A, but A doesn't
implement getB() nor getC().
But I need to have in the same vector objets of A, B and C. And I don't want
to declare getB and getC as virtual in A, because these methods will only be
defined in classes B and C respectively.

How can I do that?


You would come up with some other function name that makes sense in the general
case for all subtypes. What does it do? It just returns a value, that both B
and C seem to share. You haven't given enough information, but right off the
top if my head, I'd say take out "int c" and "int b" and put "int value" in A
instead. Then write a function "getValue" for A, and take out getB and getC.
Then you can write your code as such:

void p()
{
v->getValue();
}
*That* is the point of polymorphism. No "ifs" for the type.
 
J

Jesper Madsen

Nafai said:
That was only an example; classes A,B and C have many things in common.
Actually their only difference is what I show in that example.

There are several ways of doing this:


This way you can sort of downcast without using dynamic_cast, and you know
which classes you allow to cast to in class A. (this is what is done in the
composite pattern)

class C;
class B;

class A {
public:
virtual C* GetC() { return 0; };
virtual B* GetB(){ return 0; };
};

class B : public A{
public:
virtual B* GetB(){ return this; };
};

class C : public A{
public:
virtual C* GetC(){ return this; };
}

or you can implement methods that do nothing on the base class, and has
functionality on some of the derived...
(this is done in state pattern)

class A {
public:
virtual void doStuffB{};
virtual void doStuffC{};
};

class B: public A {
int x_;
public:
virtual void doStuffB{ x_ = 10; };
};

class C: public A {
int y_;
public:
virtual void doStuffC{ y_ = 10;};
};

You can probably solve this in more ways, but what ever way you choose to
solve it, the cleanest one would be to keep the interface between the base
class and its derivee's 100%. You might be able to use use template method
if you want something extra to happen in a derived class.. If you have a
need to add a function, that has nothing to do with the rest of the
hierachy, then take a look at the hierachy again, you might need to refactor
it..
 
N

Nafai

In Smalltalk I've seen somethings which in C++ would be like the following:

void aClass::aMethod()
{
throw("This class should not implement this method");
}
 
N

Nafai

With this example I want to show a problem when using polymorphism. The
classes A, B and C are supposed to have many things in common. I use "..."
to avoid writing them. I only focus on this difference between them (name
that methods "doSomethingWithCThatCannotBeDoneWithANorB()" instead of
"getC()") and the problem it means when using a polymorphic container.

jeffc said:
Nafai said:
Hello. I'll try to explain my problem with an example:

I have the following classes:

class A {
public:
string name;
...
};

class B : public A
{
B(int x) {b=x; name="I am B"; }
int b;
getB() { return b };
};

class C : public A
{
C(int x) {c=x; name="I am C"; }
int c;
getC() { return c; }
};

Now, I need a vector<A*> v;

An there is a function like the following:

void p()
{
...
if(v->name=="I am B"){ v->getB() ...};
...
if(v[j]->name=="I am C") { v[j]->getC() ... };
...
};

It doesn't work because v contains objects of class A, but A doesn't
implement getB() nor getC().
But I need to have in the same vector objets of A, B and C. And I don't
want
to declare getB and getC as virtual in A, because these methods will only
be
defined in classes B and C respectively.

How can I do that?


You would come up with some other function name that makes sense in the
general
case for all subtypes. What does it do? It just returns a value, that
both B
and C seem to share. You haven't given enough information, but right off
the
top if my head, I'd say take out "int c" and "int b" and put "int value"
in A
instead. Then write a function "getValue" for A, and take out getB and
getC.
Then you can write your code as such:

void p()
{
v->getValue();
}
*That* is the point of polymorphism. No "ifs" for the type.
 
J

Jeff Flinn

Nafai said:
With this example I want to show a problem when using polymorphism.
The classes A, B and C are supposed to have many things in common. I
use "..." to avoid writing them. I only focus on this difference
between them (name that methods
"doSomethingWithCThatCannotBeDoneWithANorB()" instead of "getC()")
and the problem it means when using a polymorphic container.

Plain and simple it's a bad design from an object oriented viewpoint. You've
lost encapsulation and cohesion, and are introducing unneeded coupling. Your
fighting the C++ type system rather than working with it.

I see this kind of difficult to maintain code daily in a project that was a
'C' programmer's first attempt at C++. Usually, I'll find that the behavior
that's externalized in p() below is repeated numerous places throughout the
project. So the first step is to appropriately encapsulate the behavior. See
the comments/modifications below:

This exposed data member is the first sign of something amiss. If it's not
used for any other purpose than type determination that you show below, you
can get rid of it. Otherwise make it private, or atleast protected.

Glossing over the most important aspects of this class is the second
indication of problems. Your code below shows derived classes and a
container of base class pointers. You should in that case indicate that this
class is intended to be used in that fashion with:

virtual ~A(){}

Now you should internalize the behavior of 'void p()' with:

virtual void p();

Not knowing what all your '...'s are doing you may need to have this virtual
member function take one or more arguments providing any necessary context.
Also this could be a pure virtual function (where derived classes must
define this method).

In some cases you may want to separate p()'s behavior into several virtual
member functions and make p() a non-virtual member function applying the
template pattern. Then p() would do the common operations calling the
virtual functions at the appropriate stages.


virtual void p(){ /* do B's thing here */ }

virtual void p(){ /* do C's thing here */ }

typedef vector<A*> tPtrs;

for( tPtrs::iterator lItr = v.begin() ; lItr != v.end() ; ++lItr )
{
(*lItr)->p();
}
 
N

Nafai

I am going to give a more detailed example:

// I don't type the constructors.

class Event {
private:
int time;
EventType kind;
public:
enum EventType { Event1, Event2,...,Emergency,Crime};
int when() {return time; }
EventType what() { return kind; }
}

class Emergency : public Event {
private:
int area;
public:
int where() {return area;}
};

class Crime : public Event {
private:
string person;
int aCrime;.
public:
string who() { return person; }
int whatHeDid() { return aCrime; }
};

void doSomethingWithEvents(Event* pE)
{
switch(pE->kind) {
case Event::Emergency : callAmbulance(pE->where(),pE->when());
break;
case Event::Crime: blame(pE->who(),pE->whatHeDid(),pE->when());
break;
case Event::Event1 : doSomethingRelatedToEvent1(pE->when()); break;
...
}
}

int main() {
list<Event*> lst;
.... // insert somehow Events in lst
Event* pEvt = lst.first();
lst.pop_first();
doSomethingWithEvent(pEvt);
....
}


My question is: which is the best way to do that? Is it OK like before?
 
K

KPB

Nafai said:
I am going to give a more detailed example:

// I don't type the constructors.

class Event {
private:
int time;
EventType kind;
public:
enum EventType { Event1, Event2,...,Emergency,Crime};
int when() {return time; }
EventType what() { return kind; }
}

class Emergency : public Event {
private:
int area;
public:
int where() {return area;}
};

class Crime : public Event {
private:
string person;
int aCrime;.
public:
string who() { return person; }
int whatHeDid() { return aCrime; }
};

void doSomethingWithEvents(Event* pE)
{
switch(pE->kind) {
case Event::Emergency : callAmbulance(pE->where(),pE->when());
break;
case Event::Crime: blame(pE->who(),pE->whatHeDid(),pE->when());
break;
case Event::Event1 : doSomethingRelatedToEvent1(pE->when()); break;
...
}
}

int main() {
list<Event*> lst;
.... // insert somehow Events in lst
Event* pEvt = lst.first();
lst.pop_first();
doSomethingWithEvent(pEvt);
....
}


My question is: which is the best way to do that? Is it OK like before?

In case it's not an oversight on your part, I just want to point out
that you should add a virtual destructor to your Event class. Even if
it's an empty *noop* you should still add it.

KPB
 
N

Nafai

KPB said:
In case it's not an oversight on your part, I just want to point out that
you should add a virtual destructor to your Event class. Even if it's an
empty *noop* you should still add it.

KPB

OK.

But do you think that is a good desing? How could it be done better?
 
K

KPB

Nafai said:
But do you think that is a good desing? How could it be done better?

Now that I look at it more, it's flawed in a few areas.

First of all, like I stated before, you *MUST* add a virtual destructor
to Event. If this was a simple oversight on your part, fine. However, if
you don't understand why this needs to be so, you need to research the
topic further. The FAQ is a good place to start.

Secondly, your voidDoSomethingWithEvents() is flawed. I'm guessing that
you didn't bother to try and compile this stuff because this function is
full of compilations errors.

This line for example:
case Event::Crime: blame(pE->who(),pE->whatHeDid(),pE->when());

pE is defined as Event* yet is calling "who()". Well, "who" is defined
in Crime so right here you're going to get a compilation error.

You either have to write a "who" implementation in Event or upcast (or
is it downcast?... I forget) this pointer to a pointer to Crime (which I
don't suggest here).

Try to get this function to compile. You'll see what I mean.

I think you need to research your materials (again the FAQ is a good
start) on inheritance.

KPB
 
N

Nafai

KPB said:
Now that I look at it more, it's flawed in a few areas.

First of all, like I stated before, you *MUST* add a virtual destructor to
Event. If this was a simple oversight on your part, fine. However, if you
don't understand why this needs to be so, you need to research the topic
further. The FAQ is a good place to start.

Secondly, your voidDoSomethingWithEvents() is flawed. I'm guessing that
you didn't bother to try and compile this stuff because this function is
full of compilations errors.

This line for example:
case Event::Crime: blame(pE->who(),pE->whatHeDid(),pE->when());

pE is defined as Event* yet is calling "who()". Well, "who" is defined in
Crime so right here you're going to get a compilation error.

You either have to write a "who" implementation in Event or upcast (or is
it downcast?... I forget) this pointer to a pointer to Crime (which I
don't suggest here).

Try to get this function to compile. You'll see what I mean.

I think you need to research your materials (again the FAQ is a good
start) on inheritance.

KPB
Of course I didn't try to compile this.
One solution for this to work is to add as virtual functions who(), where()
etc. in Event. But is that a GOOD SOLUTION?
 
K

KPB

Nafai said:
Of course I didn't try to compile this.

Why not? You would've seen that your function doesn't compile. When
something doesn't compile, that's not GOOD DESIGN, right?

One solution for this to work is to add as virtual functions who(), where()
etc. in Event. But is that a GOOD SOLUTION?

That would be a good start since a pointer to your base class is calling
these functions.

KPB
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
474,298
Messages
2,571,540
Members
48,276
Latest member
Kaylee93L8

Latest Threads

Top