wootscootinboogie
wootscootinboogie

Reputation: 8705

Casting down polymorphic pointers

I have a Shape (abstract) base class that Triangle and Square inherit from. Square has a method Split which returns an array of Shapes:

Shape** Square::Split(string direction, int times)
{
    if (direction == "diagonal" && times == 1)
    {
        numSplits = times;

        for (int i = 0; i < times +1; i++)
        {
            shapes[i] = new Triangle(side, side, sqrt(2) * side);
        }
    }
    else if (direction == "horizontal" || direction == "vertical")
    {
        double newSide = (double)side / 2;
        numSplits = times;
        for (int i = 0; i < times + 1; i++)
        {
            shapes[i] = new Rectangle(newSide, side);
        }
    }
    else
    {
        //to do 
    }

    return shapes;
}

in my main method I have

Square* square= new Square(3);
Shape** shapeArray = square->Split("diagonal", 1);
shapeArray[0] = (Triangle*)shapeArray[0]; //contains no Triangle members
shapeArray[0]= dynamic_cast<Triangle*>(shapeArray[0]); //still no Triangle members
int triangleType = dynamic_cast<Triangle*>(shapeArray[0])->GetType(); //contains Triangle members

I assume this is an example of object slicing? But I don't understand why I can't cast the first element in the array (which is a triangle) to a Triangle, but the last line allows me to reach into the Triangle class.

header for Square if needed

class Square :
    public Shape
{
public:
    Square();
    Square(string);
    Square(string, string);
    ~Square();
    Square(int);
    Square(string, int);

    virtual double Area();
    virtual void Save(string);
    virtual double Perimeter();
    Shape** Split(string direction,int);
private:
    string sName;
    string filePath;
    int side;
    double diagonal;
    int numSplits;
    Shape** shapes = new Shape*[numSplits];
};

Upvotes: 0

Views: 477

Answers (3)

Tony Delroy
Tony Delroy

Reputation: 106236

Shape** shapeArray = square->Split("diagonal", 1);
shapeArray[0] = (Triangle*)shapeArray[0]; //contains no Triangle members
shapeArray[0]= dynamic_cast<Triangle*>(shapeArray[0]); //still no Triangle members
int triangleType = dynamic_cast<Triangle*>(shapeArray[0])->GetType();

I assume this is an example of object slicing?

No it's not. You're dealing with pointers to your shapes - slicing happens when you try to assign the value of a derived type into an instance of one of its base classes, for a start - the base class may not be big enough (i.e. had fewer data members, had less memory reserved/allocated for it, and secondly, the assignment code may or may not copy the derived class's pointer to the derived class Virtual Dispatch Table - if that's how your compiler's supporting polymorphism). Slicing doesn't happen when you just manhandle pointers to objects (though if you then use the pointers to coordinate copying you can slice).

But I don't understand why I can't cast the first element in the array (which is a triangle) to a Triangle, but the last line allows me to reach into the Triangle class.

Your attempted casts aren't doing anything useful. They tell the compiler to treat the Shape* as a Triangle* momentarily, but then you assign that value back into a Shape* variable anyway, losing the Triangle* static type information you momentarily injected.

The final dynamic_cast<> differs in that you use the cast-to value directly ala ->GetType() - while the compiler still considers the static type to be Triangle - instead of assigning it to the Shape* and having the static type "decay" back to Shape*.

Upvotes: 1

etheranger
etheranger

Reputation: 1283

You seem to be expecting pointers to somehow "remember" what type of pointer has been stored in them. Strongly typed languages don't work like that.

Once Shape ** shapeArray; is declared, it is, and will always be, a pointer to a pointer to a Shape.

The expression shapeArray[0] has the type Shape *. You can store a pointer to a Shape that happens to also be a Triangle in it, but the only thing the compiler and runtime can assume later about the object pointed to is that it's a Shape of some kind.

The expression (Triangle*)shapeArray[0] has the type Triangle *, as does the expression dynamic_cast<Triangle*>(shapeArray[0]).

As you've found, you can access Triangle's members from any expression with the type Triangle*, but not from a Shape* because there's no way of telling what kind of Shape it might be.

Upvotes: 1

Keith
Keith

Reputation: 6834

I've put this in an answer due to length, but is not a full answer.

What do you expect from this code?

shapeArray[0] = (Triangle*)shapeArray[0]; //contains no Triangle members
shapeArray[0]= dynamic_cast<Triangle*>(shapeArray[0]); //still no Triangle members

The first line should:

  1. Take the Shape* pointer in the first element of the array
  2. Downcast it to a Triangle*

double p = shapeArray[0]-> 3. Assign back into to the first element of the array. This is of type Shape*, so requires an upcast, bringing it back to where you started.

The second line should:

  1. Take the Shape* pointer in the first element of the array
  2. Downcast it to a Triangle*, or return nullptr if it is not actually a `Triangle'.
  3. Assign back into to the first element of the array. This is of type Shape*, so requires an upcast, bringing it back to where you started.

So in short, the first line does nothing and the seconds zeros the element if it is not a Triangle.

To see whether you really have Triangle* in the array, add a line such as:

double perimeter = shapeArray[0]->Perimeter();

You can set a breakpoint or log message in the Perimeter method of Triangle to check it is being called.

Alternatively write:

Triangle* triangle = dynamic_cast<Triangle*>(shapeArray[0]); 

Upvotes: 1

Related Questions