0

I've provided an extremely simplified version of the code which reproduces the error.

class Shape {
    public:
        virtual void func()=0;
};

class Circle : public Shape {
    public:
        Circle() {  }
        void func() { }
};

class Square : public Shape {
    public:
        Square() {  }
        void func() { }
};

int main() {
    Circle c;
    std::vector<Circle> circs;

    std::vector<Shape*> shapes;

    shapes.push_back(&c);

    circs.push_back(shapes[0]); //ie, the Circle object that was just pushed into the 'shapes' vector.

}

I know that as of right now, this is functionally useless, and that I could just push the Circle object to the vector - however, in keeping with the shape analogy, my project also has triangles, squares, etc. I process data using a function which accepts Shape& as a parameter, so that I can send all shapes to one function, instead of separate functions for each shape. That's besides the point, but gives insight into why I'm doing what I'm doing in the simplified code.

The last line in this code will not work. Could anyone tell me why? Or provide me with a solution/work-around? Is this considered bad programming-style?

EDIT: So I've solved the Object Slicing issue I was having. For anyone with the same issue, look at fgp's answer in the following thread:

What is object slicing?

I used the following to help allow what I was trying to do (Move a Circle object to a Shape*, compute some things, then push the Circle (which is in the Shape* vector), to its final resting place, a Circle vector:

class Shape {
public:
    virtual Shape& operator=(const Shape& s) {
        assign(s);
        return *this;
    }

    virtual std::string getName() = 0;
    virtual         int getEdges() = 0;



protected:
    std::string name;
    int         edges;

    void assign(const Shape& s) {
        this->name = s.name;
        this->edges = s.edges;
    }
};

class Circle : public Shape {
private:
    int radius;
public:
    Circle() { name = "Circle"; edges = 1; }
    Circle(int rad) { name = "Circle"; edges = 1; radius = rad; }

    virtual Circle& operator=(const Shape& s) {
        if (const Circle* c = dynamic_cast<const Circle*>(&s))
            assign(*c);
        else{
            std::cout << "BAD ASSIGNMENT IN CIRCLE.";
                    //THROW ERROR HERE INSTEAD OF THE ABOVE COUT
            }
        return *this;
    }

    std::string getName() { return name; }
            int getEdges() { return edges; }
            int getRadius() { return radius; }
           void setRadius(int r) { radius = r; }

protected:
    void assign(const Circle& c) {
        Shape::assign(c);
        this->radius = c.radius;
    }

};

int main() {
    std::vector<Shape*> shapes;
    std::vector<Circle> circs;
    Circle c2(5); //Creates a circle with 5 for the radius.

    shapes.push_back(&c2); //Pushing the 5-radius circle into the Shapes* vector
    Circle c3; //Creates a circle with default constructor (which does NOT define radius)
    c3 = *shapes[0]; //Now, the overloaded assignment operator. Look at Circle::assign(const Shape&) function
    circs.push_back(c3); //We push our newly assigned circle to our Circle vector
    std::cout << "c3 radius: " << circs[0].getRadius(); //This will be 5!
}

It was a pleasant surprise to see this work! c3 will now know about c2's radius, showing that the overloaded assignment operators work for a Shape->Circle conversion.

If anyone has some suggestions, please let me know! (I will be creating a Circle constructor that takes a (const Shape&) param, so I can use Circle c = *shapes[0], instead of having to separate the lines since it cannot find a constructor that accepts that parameter).

EDIT2: Also, if you use this, make sure you throw an error (I left a comment where you should).

Community
  • 1
  • 1
  • Although a class is derived from another one, they're still **two different types** and thus creating a vector of base objects is different from a vector of derived objects. You can store a vector of pointers to the base class and let the polymorphism do its job. Since it appears that your code uses virtual polymorphism, I'd recommend to use pointers to the base class instead of references. – Marco A. Jun 14 '14 at 11:25
  • The problem is that while *you* know that the first shape in the list is a circle, the compiler doesn't. – IMSoP Jun 14 '14 at 11:27
  • Have a read to the question I marked as duplicate. It explains the root reason of your problem exactly. It is called Object Slicing. – Stephane Rolland Jun 14 '14 at 11:32
  • I just gave it a read through, it's very informative. So if I'm certain that my Circle shape doesn't introduce any new variables/functions not defined in the parent, slicing shouldn't be a problem? – Jhomas Tefferson Jun 14 '14 at 11:34
  • 3
    No, don't go this way. Too dangerous. Learn the correct way of doing it in C++: it is to have a collection of `Circle*`, if you want to store derived-from-Circle objects in it. – Stephane Rolland Jun 14 '14 at 11:37
  • Sorry, could you expand on that a little? EDIT: As in, how would having a collection of `Circle*` help if the circle NEEDS to go through the `Shape*` vector *first*? – Jhomas Tefferson Jun 14 '14 at 11:48
  • If you need a function that do different things when you pass a `Circle` and when you pass a `Square`, use function overloading, not casting. – T.C. Jun 14 '14 at 12:04

2 Answers2

1

This in general is a bad idea to do such things to down cast like you want. However to directly answer the question:

Circle* t = dynamic_cast<Circle*>(shapes[0]);
if(t) //make sure dynamic cast succeeded
  circs.push_back(*t);

You have to cast the reference to a Circle type, because it was a Shape.

As I mentioned before, this is not ideal. What you should really do is allow your code to work on polymorphic principles. Use the abstract base class to your advantage! Otherwise this could lead to undefined behavior, especially when shapes probably stores more than just Circles in more realistic code.

pippin1289
  • 4,861
  • 2
  • 22
  • 37
0

Although a class is derived from another one, they're still two different types and thus creating a vector of base objects is different from a vector of derived objects.

As a design point of view (and I strongly reccommend it) you can store a vector of pointers to the base class and let the virtual polymorphism do its job: use a vector of pointers to the base class instead of references.


Edit: since you asked me to expand a bit more on the design concept, this is what I had in mind:

#include <iostream>
#include <vector>
using namespace std;

class Shape {
    protected:
        Shape() {} // Having a protected constructor only allows you to
                   // call it from the same or derived class
    public:
        virtual void func()=0;
};

class Circle : public Shape {
    public:
        Circle() {  }
        void func() { cout << "Hello from a Circle object" << endl; }
};

class Square : public Shape {
    public:
        Square() {  }
        void func() { cout << "Hello from a Square object" << endl; }
};

int main() {

    std::vector<Shape*> shapes;
    Circle c;
    Square s;
    shapes.push_back(&c); // Store the address of the object
    shapes.push_back(&s); // Store the address of the object

    // A call through a pointer to a virtul polymorphic class
    // will make sure to call the appropriate function
    shapes[0]->func(); // Hello from a circle object
    shapes[1]->func(); // Hello from a square object

}

http://ideone.com/X52GLa

which is an example of runtime polymorphism where you only store a vector of base class pointers. Notice the protected constructor: it prevents you from instantiating Shape objects directly outside of the derived classes.

Marco A.
  • 43,032
  • 26
  • 132
  • 246
  • Could you possibly expand on this? Or point me in the direction of a question that does? I'm still rather new to C++. How would this aid my shape example if all the shapes (circles/squares/etc) must go through the `Shape*` vector first? – Jhomas Tefferson Jun 14 '14 at 12:01
  • Ah, this is great thank you, the `protected` construct is awesome. I'm already doing something to this effect in my actual project. I'm not sure it'll solve my issue, however. The reason I'm keeping track of a `Shape*` vector is that all shapes must be constructed. So instead of having a `decrementSquareConstruction()`, and `decrementCircleConstruction()`, I'll push all shapes into `Shape*` vector, and decrement each shape's construction length each turn. – Jhomas Tefferson Jun 14 '14 at 19:25
  • And when finished, I want to push the `Circle` that is in the `Shape*` vector, into a vector which keeps track of finalized `Circle` objects. – Jhomas Tefferson Jun 14 '14 at 19:26
  • I could use differently defined pure virtual functions, however, it's another class entirely that has the `Circle` vector that I'd like to push my `Shape*` circle object to (in keeping with the analogy, let's say I have a DrawingBoard class where the vectors in question are located [`Circle` and `Shape*`]). – Jhomas Tefferson Jun 14 '14 at 19:31
  • I don't get it: do you need to keep objects around? Why can't you keep pointers around? Do you need to know the type of the object from a pointer? (dynamic_cast or inserting an additional parameter into the constructor are two easy-to-go solutions) – Marco A. Jun 14 '14 at 19:46
  • Sorry if I explained that horribly, let me give it another go. I have a `DrawingBoard` class. The `DrawingBoard` class keeps track of `vector`, and `vector` and `vector`. Before I can use a `Circle` or `Square`, I must "construct" (construction in my game takes X amount of turns). – Jhomas Tefferson Jun 14 '14 at 19:53
  • Now, when I "construct" a `Square` or `Circle`, I push it to the `Shape*` vector. Every turn, I run through the `Shape*` vector, and decrement anything that's "under construction". When the turn counter reaches 0, I determine what shape it is, and push it to the appropriate `vector constructedCircs`, or `vector constructedSquares`. – Jhomas Tefferson Jun 14 '14 at 19:55
  • So anything that's in the `Shape*` vector is a shape that's under construction. I reasoned that this was better/cleaner than having 2 separate vectors for Circles and Squares under construction. Anything in the `Circle` or `Square` vectors are completed and awaiting user interaction. – Jhomas Tefferson Jun 14 '14 at 19:57
  • I think I just approached it horribly wrong though. `Square` doesn't add any new data-members/functions that aren't defined in `Shape`, but `Circle` does (introduces a radius/circumference). So after posting this question, I discovered that my `Circle` objects were getting sliced. – Jhomas Tefferson Jun 14 '14 at 20:01