Search code examples
c++pointersinheritanceabstract-classassignment-operator

Using pointers and assignment operator with derived class in a function that takes abstract base class as argument


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!


Solution

  • 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.