patterncppMinor
Avoiding object slicing within abstractions and their derivations
Viewed 0 times
derivationsandabstractionswithinslicingobjectavoidingtheir
Problem
I've created a rudimentary example of the issue I'm inquiring about: Object slicing. I followed a post on Stack Overflow which provided a similar template - and for all intents and purposes, this works. I was just wondering if this would get me into a trouble longer down the line? Is there anything here that jumps out at you as dangerous and/or wrong?
The heart of the logic is in
Please let me know if there are any glaringly obvious improvements.
The heart of the logic is in
main(). I'm pushing a Circle object into a Shapes vector, then push the new Shapes element into a Circle vector. The issue I had WITHOUT the assignment operators, was that the compiler couldn't decide how to convert Shape* to Circle.Please let me know if there are any glaringly obvious improvements.
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(&s))
assign(*c);
else{
std::cout radius = c.radius;
}
};
int main() {
std::vector shapes;
std::vector 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!
}Solution
Allowing a
Also I would use Curiously Recurring Template Pattern to get around having to write the assignment operator-er in every derived class.
In addition to what @Chris Jester-Young covered (so everything he said):
The Base class of a hierarchy with virtual methods should also have a virtual destructor. Otherwise you are going to start having issues with memory management when you start dynamically creating objects on the fly.
Don't define the copy constructor or assignment operator if nothing special needs to be done. Let the compiler generated version do the work for you. Unless you have something special in the destructor or have RAW pointer members this is unlikely. PS you should not have RAW pointer members either.
Don't like getters/setters. They break encapsulation and expose the internal implementation of the object. You should consider why you need to pass the state of the object out (is the function that uses the values really a member function).
Interactions on the object should usually be done internally to the object. This will get rid of most of your needs for getters and setters. If you still have a need after the analysis fine.
A vector of pointers. Red flag who owns the pointers.
A'hh you are pushing stack objects onto the vector.
Maybe it should be a vector of reference wrapper to Shape
Getting a circle from a shape.
Not sure that should really happen. The interface to
Based on comments below:
I will need to pull name's and description's to display to the user
Its easier to ask the object to display itself rather the pull details about the object and then display them.
The simplist example is just printing information to a stream:
You don't need to expose the members just because you need serialization. Doing it this ways adds consistency so that all objects of a particular type are serialized in the same way. If you need to change what it looks like you have a single place in the code that needs modifying.
We can extend the above to other graphical representations.
```
struct DisplayInterface
{
// This is an interface for drawing shapes on a display.
// You can imagine it has the standard drawing functions.
// But imagine a base set.
void drawLine(Pt const& tr, Pt const& bl);
void drawCircle(Pt const& tr center, unsigned int radius);
void drawPolygon(std::vector const& pts);
void drawText(Pt const& bl, unsigned int fontSize, std::string const& str);
};
// Now in your drawing code you pass the DisplayInterface to the object
// So it can draw itself. So usually the drawing system provides an
// interface that all drawable objects need to implement.
struct Drawable
{
virtual void display(DisplayInterface& di) = 0;
};
class Shape: public Drawable
Shape to be assigned to a circle does not sound like it is very safe. I suppose there are situations where it can be used but I would advice against it in the general caseAlso I would use Curiously Recurring Template Pattern to get around having to write the assignment operator-er in every derived class.
class Shape
{ // STUFF
};
template
class ShapeCopyable: public Shape
{
T& operator=(const Shape& s)
{
T const& c = dynamic_cast(s); // Throws on bad cast.
assign(c);
return *this;
}
};
class Circle: public ShapeCopyable
{
// Stuff
};In addition to what @Chris Jester-Young covered (so everything he said):
The Base class of a hierarchy with virtual methods should also have a virtual destructor. Otherwise you are going to start having issues with memory management when you start dynamically creating objects on the fly.
class Shape {
public:
virtual ~Shape() {}Don't define the copy constructor or assignment operator if nothing special needs to be done. Let the compiler generated version do the work for you. Unless you have something special in the destructor or have RAW pointer members this is unlikely. PS you should not have RAW pointer members either.
// Remove this:
virtual Shape& operator=(const Shape& s);Don't like getters/setters. They break encapsulation and expose the internal implementation of the object. You should consider why you need to pass the state of the object out (is the function that uses the values really a member function).
Interactions on the object should usually be done internally to the object. This will get rid of most of your needs for getters and setters. If you still have a need after the analysis fine.
// Don't like them.
// Make but sometimes you need them but minimze your
// exposure by using methods that manipulate the object rather
// than moving the manipulation to an external actor.
std::string getName() { return name; }
int getEdges() { return edges; }
int getRadius() { return radius; }
void setRadius(int r) { radius = r; }A vector of pointers. Red flag who owns the pointers.
std::vector shapes;A'hh you are pushing stack objects onto the vector.
shapes.push_back(&c2);Maybe it should be a vector of reference wrapper to Shape
std::vector>.Getting a circle from a shape.
c3 = *shapes[0];Not sure that should really happen. The interface to
Shape should be how you manipulate the object. Needing to convert the object to a circle seems unnecessary in most situations (you should probably have virtual functions that do the actions you need). Sometimes things have unique actions then you need to convert them back.But in these situations just use dynamic_cast directly (this also shows you are doing the conversion very explicitly).Based on comments below:
I will need to pull name's and description's to display to the user
Its easier to ask the object to display itself rather the pull details about the object and then display them.
The simplist example is just printing information to a stream:
// Original Way
Circle c(5);
std::cout << "Circle: " << c.getName()
<< " Edges: " << c. getEdges
<< " Radius: " << c.getRadius()
<< "\n";
// What I would do is make the serialization of the object to a stream
// a member method that does the work.
class Shape
{
virtual void print(std::ostream& stream) const = 0; // Can print a Shape
// Must be specialized for
// Specific types.
friend std::ostream& operator<<(std::ostream& stream, Shape const& shape)
{
shape.print(stream);
return stream;
}
};
// Now you print like this:
Circle c(5);
std::cout << c << "\n";You don't need to expose the members just because you need serialization. Doing it this ways adds consistency so that all objects of a particular type are serialized in the same way. If you need to change what it looks like you have a single place in the code that needs modifying.
We can extend the above to other graphical representations.
```
struct DisplayInterface
{
// This is an interface for drawing shapes on a display.
// You can imagine it has the standard drawing functions.
// But imagine a base set.
void drawLine(Pt const& tr, Pt const& bl);
void drawCircle(Pt const& tr center, unsigned int radius);
void drawPolygon(std::vector const& pts);
void drawText(Pt const& bl, unsigned int fontSize, std::string const& str);
};
// Now in your drawing code you pass the DisplayInterface to the object
// So it can draw itself. So usually the drawing system provides an
// interface that all drawable objects need to implement.
struct Drawable
{
virtual void display(DisplayInterface& di) = 0;
};
class Shape: public Drawable
Code Snippets
class Shape
{ // STUFF
};
template<typename T>
class ShapeCopyable: public Shape
{
T& operator=(const Shape& s)
{
T const& c = dynamic_cast<T const&>(s); // Throws on bad cast.
assign(c);
return *this;
}
};
class Circle: public ShapeCopyable<Circle>
{
// Stuff
};class Shape {
public:
virtual ~Shape() {}// Remove this:
virtual Shape& operator=(const Shape& s);// Don't like them.
// Make but sometimes you need them but minimze your
// exposure by using methods that manipulate the object rather
// than moving the manipulation to an external actor.
std::string getName() { return name; }
int getEdges() { return edges; }
int getRadius() { return radius; }
void setRadius(int r) { radius = r; }std::vector<Shape*> shapes;Context
StackExchange Code Review Q#54449, answer score: 9
Revisions (0)
No revisions yet.