Reputation: 3
I'm having a problem in C++. I wish to take a derived class, hold it as its base class with some other base classes. Then perform some operations on all the classes that only require them to be the base class. After this I wish to have the derived part of the class back again.
I've tried to simplify the problem as much as possible and produce a test program, given below
#include <vector> //Needed for the holding class and main
#include <iostream> //For output to terminal
class Base
{
int a; //Variable for all classes derived from base
public:
Base() { a = 13; }; //Set a to something
int get_a() { return a; }; //Access a
virtual void bugger_all() {}; //To make the class polymorphic (my first alarm that I might be doing something wrong with polymorphism
};
class Derived:public Base
{
int b;//not accessable by the base class
public:
Derived():Base() { b = 7; };//Set b and a to something
int get_b() { return b; };
void bugger_all() {}; //Implements to virtual function from the base class (just to make everything polymorphic)
};
//Holds several bases
class Holder
{
std::vector<Base> bases;//Holds a vector of base classes, not nessesarily derived classes but can be
public:
void add(Base to_add) { bases.push_back(to_add); };//Add something to the vector
Base get(int i) { return bases[i]; };//Get a certain vector
void print_all() { for(unsigned int i=0; i<bases.size(); i++) std::cout << bases[i].get_a() << "\n"; }; //Prints a for all bases, note that this is specific only to bases and can also be used for derived classes
std::vector<Base> get_all() { return bases; };//Returns the main vector
};
int main(int argc, char *argv[])
{
Derived higher = Derived(); //The derived class (extends the base class)
Base lower = Base(); //Simply a base class, for comparisons
Holder holding_class = Holder();//Will hold both the above objects
//Add the objects to the holder
holding_class.add(lower);
holding_class.add(higher);
//Prints everything in the holder
holding_class.print_all();
std::vector<Base> all_bases = holding_class.get_all(); //Get all the bases back again
std::cout << all_bases[1].get_a() << "\n"; //Test to see if we have retained a from the derived class
Derived higher_again = *(static_cast<Derived*>(&all_bases[1])); //Cast is done here, take a base class and change it to a derived class
std::cout << higher_again.get_b() << "\n"; //Output something specific to the derived class
return 0;//Exit
}
It is complied using g++, giving no errors. The program is run and the output is
13
13
13
0
If the program worked as intended I would expect the output to be
13
13
13
7
This indicates to me that 'higher_again' was cast incorrectly and its 'b' value is lost somehow and the complier has simply set the value to 0.
From looking around it seems the use of dynamic_cast and static_cast are ill advised (probably because of issues like this). However I cannot see a work around to the problem. I also realise I am probably doing something wrong in terms of polymorphism (having to create a useless virtual function). Any advice would be helpful. Thanks in advance.
Upvotes: 0
Views: 3451
Reputation: 15337
You need to distinguish between value types and reference types.
Examples of value types:
Base base;
Derived derived;
Examples of reference types
Derived* p_derived = &derived;
Base& ref_base = *p_derived;
When you declare a value type, it is exactly what you declare it to be, because memory is allocated for the object of exactly the type you specified.
When you declare a reference type, you are not allocating any more memory for objects. Rather, you have a handle to some pre-existing object. When you use a reference type, you can play with polymorphism, as referred to by the handle.
I like to think of value type as a tight variable used in declaring concrete instances: it is exactly what you say declare it to be.
In contrast, I like to use reference type as a loose handle when I want to abstract away from concretes. The actual value behind a reference type could be anything from your reference type down the inheritance hierarchy. So if you have a Base*, the actual object pointed to could be Base or anything derived from Base, including Derived.
So back to your problem. By definition, all std containers work are value based. If you want polymorphism, in this context, the trick is to store pointers to Base as your value. For example, std::vector<Base*>
, std::unique_ptr<Base>
, etc... and not std::vector<Base>
itself.
If you store std::vector<Base>
, you can only have Base instances in your vector and nothing else. So if you do attempt to store an instance that is Derived, this is called slicing because you end up cutting away all the memory that is in Derived by storing a copy of your Derived instance that is Base! Why? In std::vector<Base>
, you only have enough space to store Base
objects, because std containers are value based.
OTOH, if you store std::vector<Base*>
, the value stored is just a pointer. The size of a Base* is the same as a Derived*. There is no problem/no slicing when you store these values. As an added bonus, because you are using a looser handle, this allows the compiler to use the vtables to look up the correct polymorphic call that you really want.
So the take-away is, if you want polymorphism, use reference types.
Upvotes: 3
Reputation: 45450
This indicates to me that 'higher_again' was cast incorrectly and its 'b' value is lost somehow and the complier has simply set the value to 0.
Because you are storing Base
type in vector which cause slicing
thus Derived objects
are sliced into 'Base` objects.
Try to store pointer to base instead, the derived objects won't be sliced and polymorphism workes through pointers:
std::vector<std::shared_ptr<Base>> bases;
Upvotes: 3
Reputation: 106236
To use polymorphism as you intend, you need to store Base*s to dynamically allocated memory. When you have...
std::vector<Base> bases;//Holds a vector of base classes, not nessesarily derived classes but can be
...you're creating a vector - which is effectively a packed array of Base - in which there is no room physically for the b
data member added by the derived class.
Find a book! ;-P
Upvotes: 2