2

So, I'm on the last chapter of Programming for Games Module 1/2 and I ran into the code I've got below. I understand pointers are being created, I understand the first line of code, I pretty much understand what upcasting and downcasting are in concept, but I don't understand, specifically, what (Base*) and (Derived*) are doing here (the right hand sides of each line minus the first are confusing me).

Can someone just explain to me what this syntax is doing?

int main()
{
    Derived* derived = new Derived(); 
    Base* base = (Base*) derived; //upcast 
    Derived* derived2 = (Derived*) base; //downcast

    return 0;
}
Dmitriy
  • 3,305
  • 7
  • 44
  • 55
  • 1
    if you are only interested in the syntax itself, it's C style cast, see [here](https://www.tutorialspoint.com/cprogramming/c_type_casting.htm) which is not really recommended in c++. Better approach would be: 1. (Base*) is not needed since derived->base conversions are done implicitly. 2. Instead of (Derived*) to use dynamic_cast(base). – David Mar 16 '19 at 23:59

2 Answers2

2

Consider the following statement:

int a = (int)(3.14);

This is called explicit type-casting. You basically tell the compiler, "I want to cast the float value 3.14 to an integer" which results in truncation of the decimal part and you get the value 3 i.e. the float value 3.14 is casted to an integer value 3. The compiler can perform such type-casting implicitly for fundamental data types.

Now consider the following:

Derived* derived = new Derived();

This creates a pointer of type Derived and dynamically allocates a new object of type Derived to it.

Now, the syntax:

Base* base = (Base*) derived;

This syntax is not really required as C++ allows that a derived class pointer (or reference) to be treated as base class pointer i.e. upcasting, but not the other way around as downcasting is a potentially dangerous operation.

The syntax however explicitly tells the compiler "I want to create a pointer of type Base and assign to it, the derived pointer which I have already created. Before assigning the value, I want to cast the type of pointer derived from type Derived to type Base and then assign it to base."

Please note that casting does not convert the type of pointer derived. The syntax above does not convert the type of pointer derived to Base. It only casts it into a Base pointer before assigning the value to base.

Similarly for the third line.

This is type-casting in C-Style. Although C++ supports C-Style type-casting, we have operators like static_cast, dynamic_cast etc in C++ which do a much better job. If you are wondering why we need such operators here is an excellent explanation : Why use static_cast<int>(x) instead of (int)x?

Hope this clarifies your confusion.

2

Let's do an example. We have the following construction:

class Animal
{
public:
    virtual string sound() = 0;
    virtual ~Animal() {}
};

class Pig: public Animal
{
public:
    string sound() override { return "Oink!"; }
};

class Cow : public Animal
{
public:
    int milk; // made this public for easy code, but probably should be encapsulated
    Cow() : milk(5) {}
    string sound() override { return "M" + string(milk, 'o') + "!"; } // a Moo! with milk times many Os
};

What we now want to do is to store a list of animals. We can't use vector<Animal> because that would contain instances of the class Animal, which is purely abstract - Animal::sound is not defined.

However, we can use a vector of pointers:

vector<Animal*> animals;
animals.push_back(new Cow);
animals.push_back(new Pig);
animals.push_back(new Cow);

for(Animal* animal : animals)
{
    cout << animal->sound() << endl;
}

So far, so good. But now take a look at the class Cow, there is the member milk which has an influence on the output. But how do we access it? We know that the first and third entries in animals are of type Cow, but can we use it? Let's try:

animals[0]->milk = 3;

This yields:

error: 'class Animal' has no member named 'milk'

However, we can do this:

Cow* cow = (Cow*) animals[0];
cow->milk = 3;

for(Animal* animal : animals)
{
    cout << animal->sound() << endl;
}

What we done here is to create a pointer to Cow from a pointer to Animal of which we knew that it was actually pointing to an object of type Cow.

Note that this is risky - here, we knew that the first entry in the vector is of that type, but in general, you don't. Therefore, I recommend that you use safe casting, that is especially dynamic_cast. If you come to understood pointer casting and feel safe in that topic, read some tutorial on how to use dynamic_cast.

Right now, we have cast the base class to the derived class, but we can also do the opposite:

Cow* cow = new Cow;
cow->milk = 7;
animals.push_back((Animal*) cow);

for(Animal* animal : animals)
{
    cout << animal->sound() << endl;
}

I assembled all of this into http://www.cpp.sh/6i6l4 if you want to see it in work.

Hope that this gives you a better understanding of what we need this for. Storing a list with objects of different types but a common base class is quite usual, and likewise is pointer to unknown subtypes. If you want more practical examples, ask. Thought about providing one, but don't want to overwhelm you for the start.

(Lastly, we need to clean up our memory, as we put our variables in the heap:

for(Animal* animal : animals)
{
    delete animal;
}
animals.clear();

if we wouldn't do it, we'd have a memory leak. Better would have been to use smart pointers like shared_ptr - again here the recommendation to read into that when you feel safe in the base topic.)

Aziuth
  • 3,652
  • 3
  • 18
  • 36