Bg1987
Bg1987

Reputation: 1159

polymorphism in c++, loss of type in vector of parent class

I have a class A, which is parent to classes B and C. And a class X, which is a parent to Y and Z.

class A {};
class B : public A {};
class C : public A {};


class X
{
    void foo(A) { std:: cout << "A"; }
};

class Y : public X
{
    void foo(B) {std::cout << "B"; }
};

class Z : public X
{
    void foo(c) {std<<cout <<"C"; }
};

int main()
{
    B b;
    C c;

    Y y;
    Z z;

    y.foo(b);//prints B // b is a B, and Y::foo takes a B, hence print B
    y.foo(c);//prints A // mismatch between types, fall back and print A
    z.foo(b);//prints A // mismatch between types, fall back and print A
    z.foo(c);//prints C // c is a C, and Y::foo takes a C, hence print C

    std::vector<A> v;
    v.push_back(b);
    v.push_back(c);

    //In this loop, it always prints A, but *this is what I want to change* 
    for (size_t i = 0; i < v.size(); ++i)
    {
        z.foo(v.at(i));
        y.foo(v.at(i));
    }
}

Is it possible to get the items to print the same result as the hard coded calls? Meaning that I will treat them as their original type, and not its parent type? or once I put them int a vector of A they will forever be of type A?

Upvotes: 3

Views: 2473

Answers (2)

Grizzly
Grizzly

Reputation: 20191

This is called slicing. When you push_back your elements into a std::vector<A> it basically copies the elements into newly constructed instances of A. Therefore the part ofs of the object, which come from the derived class will be lost ("sliced off").

In order to avoid slicing you need to use a container which stores pointers instead of elements, so you should use a std::vector<A*> or if your elements are heap allocated preferably a vector of some sort of smartpointer (std::shared_ptr or std::unique_ptr in C++11, boost::shared_ptr or std::tr1::shared_ptr otherwise).

However your code won't work as written, even if you change that: X, Y and Z all take their parameter by value, while all elements in your vector will have the type A*, so dereferencing them would yield A, so it will still call the wrong method. This could be solved by changing the signatures to always take A& or A* and using dynamic_cast to try casting that into the type:

class X
{
    void foo(A*) { std:: cout << "A"; }
};

class Y : public X
{
    void foo(A* p) { 
      if ( dynamic_cast<B*>(p) ) std::cout << "B"; // requires virtual methods in A
      else                   X::foo(p);
    }
};

class Z : public X
{
    void foo(A*){ 
      if ( dynamic_cast<C*>(p) ) std::cout << "C"; // requires virtual methods in A
      else                   X::foo(p);
    }
};

Of course dynamic_cast is a bit costly, but if that's a problem you might want to rethink your design. Furthermore you need to ensure that A, B, C contain some virtual methods (a virtual destructor would be a good idea here anyways), since otherwise dynamic_cast won't work)

Upvotes: 1

Alok Save
Alok Save

Reputation: 206546

What you are seeing is Object Slicing.
You are storing object of Derived class in an vector which is supposed to store objects of Base class, this leads to Object slicing and the derived class specific members of the object being stored get sliced off, thus the object stored in the vector just acts as object of Base class.

Solution:

You should store pointer to object of Base class in the vector:

vector<X*> 

By storing a pointer to Base class there would be no slicing and you can achieve the desired polymorphic behavior as well by making the functions virtual.
The right approach is to use a suitable Smart pointer instead of storing a raw pointer in the vector. That will ensure you do not have to manually manage the memory, RAII will do that for you automatically.

Upvotes: 8

Related Questions