1

This is just for learning purposes as I cannot seem to find such an answer any where else.. So I have multiple questions.. I won't do such a thing but I just want to know because my brain requires me to know or I'll be uncomfortable the rest of the day.

Assume I have the following classes:

class Control
{
    //virtual stuff..
};

class Button : public Control
{
    //More virtual stuff..
};

class CheckBox : public Control
{
    //More virtual stuff..
};

Thus Button and CheckBox are sisters of the same mother Control.

Now assume someone curious like moi sees something like this:

std::vector<Control> ListOfControls;    //Polymorphic Array.
ListOfControls.push_back(Button());     //Add Button to the Array.
ListOfControls.push_back(CheckBox());   //Add CheckBox to the Array.

How can I tell what datatypes that Array holds? How can I tell that ListOfControls[0] holds a Button and ListOfControls[1] holds a CheckBox? I read that you'd most likely have to do a dynamic cast and if it does not return null, it is that specific datatype:

if (dynamic_cast<Button*>(ListOfControls[0]) == ???) //I got lost.. :(

Another thing to get off my mind:

Assuming the same classes above, how do you tell for:

std::vector<void*> ListOfControls;          //Polymorphic Array through Pointer.
ListOfControls.push_back(new Button());     //Add Button to the Array.
ListOfControls.push_back(new CheckBox());   //Add CheckBox to the Array.

Is there a way to do both the above examples without a dynamic cast or is there some sort of trick to get around it? I read that dynamic cast is usually never wanted..

Finally, can you down-cast from Parent to child?

Button Btn;
Control Cn;

Btn = (Button) Cn; //???
Brandon
  • 22,723
  • 11
  • 93
  • 186

1 Answers1

3

You can't have a container of polymorphic objects because of slicing; you'll need to have a container of some kind of pointer to the polymorphic objects. You're right otherwise though; you'd have to use dynamic_cast or typeid to get the runtime type of the object the array contains pointers to.

However, you should try to write code that doesn't depend on the real type of a polymorphic object; you should generalise the interface in the base class and just call those member functions on the pointer. This makes your code much cleaner and more extensible with minimal modification of existing code.

Community
  • 1
  • 1
Seth Carnegie
  • 73,875
  • 22
  • 181
  • 249
  • :S But I compiled the above and it didn't complain.. What is slicing? Any links? TypeID seems to say that they are all "7Control" – Brandon Mar 10 '13 at 22:51
  • @CantChooseUsernames yes, a very good link: http://stackoverflow.com/questions/274626/what-is-the-slicing-problem-in-c – Seth Carnegie Mar 10 '13 at 22:52
  • 1
    @CantChooseUsernames yes, you said it: TypeID seems to say that they are all Control. That is the effect of slicing; all the information from the derived class is lost, and you really have just a vector of plain `Control`s, that aren't instances any derived class underneath. Also as an aside, `typeid` happens at compile time unless you use it on a pointer or reference to a polymorphic type. – Seth Carnegie Mar 10 '13 at 22:54
  • Wow! Ohh man.. Didn't expect that. How does this not happen in Java and C#? You know: Object O = new Integer(9); This is valid in java why? I am going to accept your answer when that timer thing runs out. But what do you mean by: "you should generalise the interface in the base class and just call those member functions on the pointer." – Brandon Mar 10 '13 at 22:57
  • 2
    @CantChooseUsernames because in Java, `Object` is really a pointer, not a value. In C++, if you make a class called `Object`, `Object o` means that you allocate a certain amount of memory for the object and name that memory location `o`, so you have to know exactly what is going to go in that memory. That's why slicing happens; you have a certain amount of memory for the base class, and derived classes can't fit in that memory because they may have extra information. However, since all types of pointers take up the same amount of memory, you can use containers of pointers instead. – Seth Carnegie Mar 10 '13 at 22:59
  • @CantChooseUsernames by "generalise the interface" I mean, put all the general functions that you need to call in the base class so that you don't have to cast them to the derived types. – Seth Carnegie Mar 10 '13 at 23:00
  • Ahh thank you! Very very helpful! Bookmarked this for future reference. – Brandon Mar 10 '13 at 23:01
  • @CantChooseUsernames also when you get more familiar with C++, you can study the [visitor pattern](http://en.wikipedia.org/wiki/Visitor_pattern) by which you can indirectly get the real type of an object, but without having to cast it or use `typeid`. – Seth Carnegie Mar 10 '13 at 23:02
  • +1 just because. well i would have mentioned also visitor pattern, which is the crisis solution when extending base interface becomes messy. and perhaps even more emphasis on ungoodness of downcasting, but +1. :-) – Cheers and hth. - Alf Mar 10 '13 at 23:20
  • @Cheersandhth.-Alf thanks, and actually I did mention the visitor pattern in a comment. And yeah, downcasting is almost always a sign of bad design, but I wanted to keep things as focused as possible for this particular answer/asker. – Seth Carnegie Mar 10 '13 at 23:28