1

I am currently taking a course in c++, coming from a java background.

We've now gotten to the point where we talk about inheritance. This week's class didn't really explain what was needed to do the weekly assignments, leaving me somewhat at loss trying to solve them.

While I will be getting around to asking my tutor himself, that'd mean waiting until next week, putting me behind.

Allow me to demonstrate what I want to do:

class base
{
   int field1;

   base(int _field): 
   field1(_field){}
};

class subclass : public base
{
   int field2;

   public:
   subclass(int _field2):
      base(_field1),field2(_field2){}


   string to_string()
   {
       return "I am a subtype!!";
   }
};

int main()
{
    vector<base> x;
    x.push_back(subclass(123,456));
    cout<<x[0].to_string[]<<end; //error!!
}

To my surprise, this code won't work, it won't even compile. The container x will cast the objects within it to the basetype, this means I am forced to treat what was once subtypes, as basetypes, which means that the subtypes variables and functions are out of reach.

What I want to do, is to treat the objects within the container as subtypes, IE typetesting(in itself a non-trivial matter) them and then do different things depending on what type the objects are.

This was trivial in java. There I could simply fill, say, an array of type base with subtypes and then go about doing whatever I wanted with the objects within.

I am assuming what I did in java, is supposed to be possible in C++, being able to place subtypes into some sort of container and then treat them as subtypes ought to be possible. It is evident I have missunderstood something fundamental here, this problem was entirely unforeseen to me.

It is worth mentioning that I tried this with both vector, and simple arrays. Exact same pattern emerged, the type of the array dictates how I can access the objects, ie I can't call to_string().

So to summarize: I wish to know how I can contain subtypes in containers without having my container casting the objects inside to the container's type.

Praetorian
  • 106,671
  • 19
  • 240
  • 328
Deviatore
  • 465
  • 1
  • 4
  • 10
  • 1
    You need to use pointers or references, and not value types, if you want to be able to use polymorphism. References are not assignable, so you can't create a `vector` of references. In your case use `vector>` assuming you actually need a `vector` of `base`s and not `subclass`s (and don't forget to give `base` a virtual destructor in that case) – Praetorian Mar 05 '15 at 18:09
  • It's often a good idea to make copy-constructors and copy-assignments of base-classes protected in order to help prevent this slicing problem. – Mooing Duck Mar 05 '15 at 18:12
  • Also, the *subtype degeneration* you refer to is called [slicing](https://stackoverflow.com/questions/274626/what-is-object-slicing) – Praetorian Mar 05 '15 at 18:14

1 Answers1

2

This was trivial in Java. There I could simply fill, say, an array of type base with subtypes and then go about doing whatever I wanted with the objects within.

The main difference between objects in Java and in C++ is that all Java objects are accessed through a reference (somewhat similar to C++ smart pointers) while in C++ you have an option of accessing objects directly. This works fine and saves a lot of memory and some CPU cycles with non-polymorphic objects, but breaks spectacularly once inheritance enters the stage due to object slicing.

It's a problem of putting ten pounds of stuff into a five-pound bag: vector<base> allocates enough space to fit N items of type base, so when you try to put subclass into the same space, the compiler tells you that it is not going to fit.

To work around this issue, emulate what Java does, i.e. use smart pointers:

vector<unique_ptr<base> > x;
unique_ptr<base> item(new subclass(123,456));
x.push_back(std::move(item));

Demo.

Note: There are several issues in your code that you need to fix in order to make this work. First, add a public to_string member function to base. Then, replace the call of to_string through the dot . operator with a call through the arrow -> operator. Finally, fix a few typos - the square brackets in the call of to_string, and a missing l in endl.

Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • Interesting, but how would I then access my object? for instance, cout< – Deviatore Mar 05 '15 at 19:23
  • @user2230627 That's because `unique_ptr` works like a pointer, so it takes the arrow `->` operator for derefernse instead of the dot `.` operator (i.e. you need `x[0]->to_string()`). – Sergey Kalinichenko Mar 05 '15 at 19:28
  • Right, forgot about that. Still won't work though. Now it says: "Undefined reference to 'base::to_string()' and below that: "collect2: error: ld returned 1 exit status Still seems to be hung up on the base class. – Deviatore Mar 05 '15 at 19:40
  • Might add I am using g++ with flag -std=c++11, version is reported to be 4.8.2(gcc). – Deviatore Mar 05 '15 at 19:44
  • @user2230627 Ah, of course, that's because `base` does not have `to_string`. You would get the same effect in Java if you add a method only in the subclass, and try calling it through a reference to the base class (`toString` does not count because it's in Java's `Object`, which has no counterpart in C++). You need to add a virtual `to_string` member function to the base class, and either give it an implementation, or make it abstract using the `virtual string to_string() = 0;` syntax. Don't forget to make it public, otherwise you wouldn't be able to call it from `main()`. – Sergey Kalinichenko Mar 05 '15 at 19:56
  • Success! After some fiddeling I finally got it to work. Much appreciated! – Deviatore Mar 05 '15 at 20:18