4

I have a function (modShape) which takes an abstract base class (Shape) as an argument; within the function I want to make a copy of the input object, modify the copy, and reassign the copy to the input object so that the modifications are retained above the scope of modShape.

I have set up a clone() member function for making the initial copy and that seems to work well. Next, I modify the copy using the doubleArea() member function, and attempt to copy it back to the input object.

The base and derived classes are defined in header.h:

#ifndef HEADER_H_
#define HEADER_H_

#include <iostream>
#include <cmath>

using namespace std;

// Abstract base class.
class Shape {
 public:
  // Virtual functions
  virtual double area() { return 0; }
  virtual double perimeter() { return 0; }
  virtual void doubleArea() { /* do nothing */ }
  virtual Shape* clone() const = 0;

};

// Derived class.
class Circle: public Shape {
 private:
  double radius;

 public:
  Circle (double r) : radius(r) {}
  double area() { return (M_PI*pow(radius,2)); }
  double perimeter() { return (M_PI*2*radius); }
  void doubleArea() { radius *= pow(2,0.5); }
  Circle* clone() const { return new Circle(*this); }

};

#endif

The function modShape and the test code are in main.cpp:

#include <iostream>
#include "header.h"

using namespace std;

void modShape(Shape &inShape) {

  // Make new Shape* from clone of inShape
  // and double its area.
  Shape* newShape = inShape.clone();
  newShape->doubleArea();
  cout << "newShape's area (after doubling): " << newShape->area() << endl;

  // Copy newShape to inShape.
  inShape = *newShape;
  cout << "newShape copied to inShape (circ)." << endl;
  cout << "inShape's area in modShape: " << inShape.area() << endl;

};


int main() {

  Circle circ(2);
  cout << "circ's initial area (in main): " << circ.area() << endl;
  modShape(circ);
  cout << "circ's final area (in main): " << circ.area() << endl;

  return 0;
}

The output I get from this function is:

circ's initial area (in main): 12.5664

newShape's area (after doubling): 25.1327

newShape copied to inShape.

inShape's area in modShape(): 12.5664

circ's final area (in main): 12.5664

So clearly the assignment inShape = *newShape is not working as I expect it to. My guess is that the assignment operator being used is for the Shape class, thus not copying member variables from the derived class (like radius)? If this is the case, I think I want to define an assignment operator which will "know" that the objects are derived classes, even though they are defined as base classes, but I'm not sure how to do that. Or if there is a better solution, please let me know! Any advice is greatly appreciated.

Update: Looks like slicing is the problem, now I need to figure out how to avoid it. I thought if I defined my function to take in a pointer, things would work better:

void modShape2(Shape* inShape) {
  Shape* newShape = inShape->clone();
  cout << inShape->area() << endl;
  inShape = newShape;
  cout << inShape->area() << endl;
}

Which I set up as:

Circle *circ2 = new Circle(1);
cout << "circ2's initial area (in main): " << circ2->area() << endl;
modShape2(circ2);
cout << "circ2's final area (in main): " << circ2->area() << endl;

The output produced here is

circ2's final area (in main): 3.14159

3.14159

6.28319

circ2's final area (in main): 3.14159

In this case it seems like the copy is happening without slicing since the area is being doubled inside of the modShape2 function, but the changes are not carried through when we go out of modShape2's scope for some reason. I'm really puzzled by this!

t354
  • 567
  • 4
  • 12
  • In your "update", remember C++ uses pass-by-value, so changing the local variable `inShape` has no effect outside the function – M.M Feb 23 '16 at 00:30
  • The topic of assignment operators for a polymorphic hierarchy is a difficult one , [see this thread](http://stackoverflow.com/questions/669818/virtual-assignment-operator-c) for some discussion and approaches. One option of course is to just disable the assignment operator and require the user to (delete and) clone. – M.M Feb 23 '16 at 00:34
  • You're right! I also tried modifying the object being pointed to, but I believe that resulted in the same slicing problem. – t354 Feb 23 '16 at 01:43

1 Answers1

2

The problem

You've identified it well. The error is caused by the following statement, which causes object slicing:

inShape = *newShape;

So only the members in the Shape base object is copied. The area, which doesn't belong to the base class is not copied.

How to sole it ?

Defining a virtual assignment operator is not advisable, as the usual signature of this operator for a class T is:

 T& operator= (const T& r);   

So that you would have troubles with the return type.

An easier solution, would be to have a virtual copy function (similar principle than your clone function):

class Shape {
    ...
    virtual void copy(const Shape&r) = 0; 
};

It would be implemented for the derived objects, checking if the types match and use the type's assignment operator. For example:

void copy(const Shape&r) override {
    if (dynamic_cast<const Circle*>(&r)) 
        *this = *dynamic_cast<const Circle*>(&r);
    else throw (invalid_argument("ouch! circle copy mismatch"));
}

And here an online demo.

Christophe
  • 68,716
  • 7
  • 72
  • 138
  • Thanks for the response, I thought slicing was the problem but didn't know the correct name for it! I tried re-defining my function to take a pointer to the object, however, the changes to the object aren't carried out of the function's scope (see update in my post). – t354 Feb 23 '16 at 00:24
  • I've completed the answer with an approach allowing you to keep the logic almost as before. – Christophe Feb 23 '16 at 00:32
  • `T& operator= (const T& r);` is the *copy-assignment operator*, however you can have `Der& operator=(const Base& r);` which does override the base class's copy-assignment, using covariant return type. Your suggested `copy` function may as well be named `operator=` . – M.M Feb 23 '16 at 00:35
  • The reason why your second pointer pased version doesn't work is that you modifiy only the poitner parameter local to the `modShape2()`. You could make that version work by passing the pointer by reference. However this code would be unsafe: overwriting the pointer would lose the pointer to the original object, so that you would have memory leaking. – Christophe Feb 23 '16 at 00:35
  • @Christophe: thank you very much for the clear solution and answering my other questions! This works very well for me. – t354 Feb 23 '16 at 00:46