3

Why doesn't this c++ code work? It is meant to dynamically cast a base class into a derived class. How would I achieve this?

    class base {
    public:
        int x = 0;
    };

    class a : public base {
    public:
        char c = 'a';
    };

    class b : public base {
    public:
        long int d = 'b';
    };

    std::vector<base> vec;
    for (int i = 0; i < 5; i++) {
        b temp;
        vec.push_back(temp);
    }

    for (int i = 0; i < 5; i++) {
        b* temp = (b*)&vec[i];
        std::cout << temp->d << std::endl;
    }
Garrett Page
  • 127
  • 7

2 Answers2

8

Whenever you push an object of b into vector vec of Base Objects, you create another object from temp which is purely of type base. You might be thinking (and you are not right!) that element which is being stored in vector will be of type Base but it will be holding an object of type bbut it's not how you achieve Dynamic Polymorphism in C++.

The statements:

std::vector<Base> vec; // 1
b temp;                // 2
vec.push_back(temp);   // 3

The third line will create a different object to type Base by calling assignment operator of base class Base operator=(const Base& ).

Also,b* temp = (b*)&vec[i]; is an undefined behavior because you are explicitly trying to cast a pointer to object of base to it's derived class type b but it doesn't hold object of type b and hence, you may get unexpected behavior.

NOTE:

Use dynamic_cast for casting between base and derived class as it will make sure that the conversion is valid. Otherwise, it will return nullptr. However, you need to make your base class polymorphic by having at least 1 virtual function.

If the cast is successful, dynamic_cast returns a value of type new-type. If the cast fails and new-type is a pointer type, it returns a null pointer of that type. If the cast fails and new-type is a reference type, it throws an exception that matches a handler of type std::bad_cast.

SOLUTION:

Use vector of pointers to base class to achieve run-time polymorphism.

std::vector<base *> vec;
for (int i = 0; i < 5; i++) {
    b *temp = new b();
    vec.push_back(temp);
}

for (int i = 0; i < 5; i++) {
    b* temp = dynamic_cast<b *>(vec[i]); // For dynamic cast, you need to add virtual functions to your base class
    if (temp != nullptr)
        std::cout << temp->d << std::endl;
}

EDIT:

Object Slicing is also a solution for your problem. Object Slicing is the right keyword for this type of problems. Here is the definition of the Object Slicing

Object slicing happens when a derived class object is assigned to a base class object, additional attributes of a derived class object are sliced off to form the base class object.

I am quoting one of the answer in the link below. See this answer and answer for the best explanation and possible solution with some code snippet. See this article, it has explained the problem when pushing object of Derived class in a vector of base class.

"Slicing" is where you assign an object of a derived class to an instance of a base class, thereby losing part of the information - some of it is "sliced" away.

For example,

class A {
   int foo;
};

class B : public A {
   int bar;
};

So an object of type B has two data members, foo and bar.

Then if you were to write this:

B b;
A a = b;

Then the information in b about member bar is lost in a.

Community
  • 1
  • 1
abhiarora
  • 9,743
  • 5
  • 32
  • 57
2

Your problem is caused by object slicing. You expect base to be a reference type, like it would be in C# or Java, but in fact, it's a value type in C++.

Reference vs value type:

There are two kinds of types in C#: reference types and value types. Variables of reference types store references to their data (objects), while variables of value types directly contain their data.

What actually happens in your code: You push an instance of b to a vector<base>. Since class b is not a class base, a new instance of base will be created in the vector using the implicitly generated copy constructor, but only the base portion of the b instance will be copied, hence "slicing down" the member d from the b instance.

The solution is to store pointers to base. So, in your case the vector should store raw (base*) or managed (unique_ptr<base> or shared_ptr<base>) pointers (prefer managed pointers to raw ones). It works, because it doesn't matter if you refer to an instance of b in memory through a b* or a base*, it still point to the same b object.

#include <iostream> // endl, cout
#include <memory> // make_unique, unique_ptr
#include <vector> // vector

class base {
public:
    int x = 0;
};

class a : public base {
public:
    char c = 'a';
};

class b : public base {
public:
    long int d = 'b';
};

int main() {
    std::vector<std::unique_ptr<base>> vec;

    for (int i = 0; i < 5; i++) {
        vec.push_back(std::make_unique<b>());
    }

    for (int i = 0; i < 5; i++) {
        const b* temp = static_cast<const b*>(vec[i].get());
        std::cout << temp->d << std::endl;
    }

    return 0;
}

Side note: You used C-style cast ((b*)), I changed it to static_cast, a better solution would be to use dynamic_cast, but that cannot be used in this case because your base class is not polymorphic (you need at least 1 virtual function for that).

Doeus
  • 430
  • 1
  • 3
  • 7