0

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.

Sean C
  • 3
  • 1
  • 2

3 Answers3

3

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;
Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
billz
  • 44,644
  • 9
  • 83
  • 100
3

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.

kfmfe04
  • 14,936
  • 14
  • 74
  • 140
  • I think I'm going to have to go back to square one with c++ at some point. Thanks for the help. – Sean C Jan 18 '13 at 04:07
2

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

Tony Delroy
  • 102,968
  • 15
  • 177
  • 252
  • Thanks for the help. Most of the books I read don't seem to mention std::vector very much or get into how memory is thrown around. Can you recommend something that might help me grasp the fundamentals of memory in c++? – Sean C Jan 18 '13 at 04:05
  • Hi Sean... the issues here would be the same if you had a single Base object and tried to copy a Derived object into it. It's known as "slicing" - that term will hopefully help you Google some relevant tutorial or FAQ discussions. For books generally, I'm afraid I've never made an effort to peruse the new-to-C++ offerings systematically - I learnt C++ from "The C++ Programming Language" by Bjarne Stroustrup, but had extensive experience with C and other languages beforehand. There are stackoverflow questions about books though - am sure the C++ subject's top questions list will have advice. – Tony Delroy Jan 18 '13 at 08:21