0

I have a base class Shape that has derived classes like Ellipse and Rectangle.

In one function, I have a variable:

Shape activeShape(black, black, {0,0}, {0,0}, false);

and later in that function:

activeShape = updateShape(isButton, activeShape, true);

updateShape looks like this:

Shape updateShape(int button, Shape active, bool leftClick)
{
    switch(button)
    {
        case 1:
            return active;
        case 2:
            return Line(active.getFillColor(), active.getBorderColor(), active.getP1(), active.getP2(),false);
            break;
        case 3:
            return Rectangle(active.getFillColor(), active.getBorderColor(), active.getP1(), active.getP2(),false);
            break;
        case 4:
            return FilledRectangle(active.getFillColor(), active.getBorderColor(), active.getP1(), active.getP2(),false);
            break;
        case 5:
            return Ellipse(active.getFillColor(), active.getBorderColor(), active.getP1(), active.getP2(),false);
            break;
        case 6:
            return FilledEllipse(active.getFillColor(), active.getBorderColor(), active.getP1(), active.getP2(),false);
            break;
        default:
            if(leftClick)
            {
                active.setColor(getEnumColor(button), active.getBorderColor());
            }
            else
                active.setColor(active.getFillColor(), getEnumColor(button));
            break;
    };
    return active;
}

So as I'm returning things like Rectangle, they are being casted as a Shape. Which is exactly not what I want.

What do I need to do to get activeShape to become one of Shapes derived classes?

Jean
  • 115
  • 1
  • 8
  • 2
    Possible duplicate of [What is object slicing?](http://stackoverflow.com/questions/274626/what-is-object-slicing) – LogicStuff Oct 03 '16 at 05:09
  • Return some smart pointer to a fresh instance of the subclass. – Basile Starynkevitch Oct 03 '16 at 05:19
  • 1
    What @BasileStarynkevitch said, you can return a smart pointer to dynamically allocated object. That corresponds to what happens under the hood in a language like C# or Java. You can think of it this way: there's no way the caller can know the size of the shape object that will be returned, so the memory for that object has to be allocated by the function, not by the caller. – Cheers and hth. - Alf Oct 03 '16 at 06:00

3 Answers3

5

Once an object is created, it is not possible to morph its type (e.g. based on information about a button obtained at run time). Returning a Rectangle as a Shape has the effect of slicing the object, so the caller only receives a copy of the Shape part of the Rectangle, but not the remaining parts.

Assuming Shape is a polymorphic base class (i.e. it provides virtual functions that may be specialised by derived classes), a Shape * (pointer to shape) can point at a Rectangle. However, it is then necessary to manage lifetime of the object correctly.

You can deal with all this by using a smart pointer such as, in C++11 and later, std::unique_pointer<Shape>.

std::unique_pointer<Shape> updateShape(int button,
        std::unique_pointer<Shape> active, bool leftClick)
{
    switch(button)
    {
        case 1:
             break;    // don't change active
        case 2:
            active = new Line(active->getFillColor(), active->getBorderColor(), active->getP1(), active->getP2(),false);
            break;
        case 3:
            active = new Rectangle(active->getFillColor(), active->getBorderColor(), active->getP1(), active->getP2(),false);
            break;
        case 4:
            active = new FilledRectangle(active->getFillColor(), active->getBorderColor(), active->getP1(), active->getP2(),false);
            break;
        case 5:
            active = new Ellipse(active->getFillColor(), active->getBorderColor(), active->getP1(), active->getP2(),false);
            break;
        case 6:
            active = new FilledEllipse(active->getFillColor(), active->getBorderColor(), active->getP1(), active->getP2(),false);
            break;
        default:
            if(leftClick)
            {
                active->setColor(getEnumColor(button), active->getBorderColor());
            }
            else
            {
                active->setColor(active->getFillColor(), getEnumColor(button));
            }
            break;
     }
     return active;
}

The reason this works is that std::unique_pointer manages the lifetime of a dynamically allocated (created with operator new) object. It does this by storing a pointer to the object, and assigning to a std::unique_pointer<Shape> changes the pointer (to make it point at a different object). Importantly, assigning to a smart pointer also releases the object it previously managed.

Note that, since std::unique_pointer will use operator delete to destroy the contained object when its lifetime is over, Shape MUST have a virtual destructor. Failure to do that will cause undefined behaviour on the use of operator delete.

The usage of this would go something like

 std::unique_pointer<Shape> activeShape(new Rectangle( whatever_parameters));

 activeShape = updateShape(button, activeShape, leftClick);

Bear in mind that activeShape is a smart pointer. So use of the contained Shape object requires pointer syntax (activeShape->whatever) not member syntax (activeShape.whatever).

Because the active parameter is passed (by value) to the function and returned, it is NECESSARY to assign the return value. If, instead of

activeShape = updateShape(button, activeShape, leftClick);

you simply

updateShape(button, activeShape, leftClick);   // returned object is discarded

(i.e. don't assign the return value to anything), the net effect is that the object held by activeShape will be destroyed, and any attempt to use it will give undefined behaviour.

Peter
  • 35,646
  • 4
  • 32
  • 74
  • 2
    If your `unique_pointer` is a `unique_ptr`, this code won't compile since you cannot copy `activeShape` into a parameter of `updateShape`. – Holt Oct 03 '16 at 07:01
2

Types are determined at compile time, so you cannot change the type of a variable based on runtime information, such as the value of button.

Instead, you would need to use something like dynamic_cast<Rectangle&>(active_shape) to test to see if it is a rectangle and then use it as such if that test passes (it will throw an exception if the shape is not a rectangle)

edit: You'd have to change your function to not return Shape by value, though. I'd suggest returning std::unique_ptr and the return statements would look like:

return std::make_unique<Ellipse>(active.getFillColor(), active.getBorderColor(), active.getP1(), active.getP2(),false);
xaxxon
  • 19,189
  • 5
  • 50
  • 80
  • Could you give me an example of where I would use `dynamic_cast`? – Jean Oct 03 '16 at 05:18
  • 2
    This won't actually work, since the function returns by value. It therefore, logically, returns a copy of the `Rectangle` or `FilledEllipse`, but sliced so it is only a copy of the `Shape` part of that object. – Peter Oct 03 '16 at 05:26
  • Yes, you'd probably want to return a std::unique_ptr – xaxxon Oct 03 '16 at 05:35
0

In below line in your code object slicing will happen, So your code is anyway problematic.

activeShape = updateShape(isButton, activeShape, true);

Here, if updateShape returns any type other than shape then your object will be sliced to shape type before returning. So, activeShape may contain sliced object which is wrong.

To improve it, you should return pointer to base class(shape) and work with it.

SACHIN GOYAL
  • 955
  • 6
  • 19