In the C++ Inheritance example, we saw how derived classes can inherit from base classes. This example resulted in code that required if() statements to calculate a function for the different classes. Polymorphism can be used instead to specify different behaviors for the different classes to avoid conditional statements in the code, which are hard to maintain and extend.

C++ Polymorphism

  • a function or operator works different when used in different context
  • can implement when classes inherit from each other
  • the result is a single action or function that produces different results based on the derived class
  • virtual: C++ function that gets overridden at runtime

Set up base class as before. This time, the shared object is the function, area(). Note that the area function is proceeded by virtual and set to equal 0. This is done to specify this is a function being overwritten by functions from the child class.

class Shape {
  public:
    //constructor
    Shape() {}
    
    virtual ~Shape() {}
    
    // Virtual function to calculate area
    virtual double area() = 0;
    
  };

The child classes are then specified. Again, we use virtual to indicate this function is overwriting the function in the base class.

class Circle : public Shape {
  double radius;
  
  public:
    Circle(double r) : Shape(){
      radius = r;
    }
  
  // Override area() for Circle
  virtual double area() {
    return M_PI * radius * radius;
  }
};

class Rectangle : public Shape {
  private:
    double length, height;
  
  public:
    Rectangle(double l, double h) : Shape(){
      length = l;
      height = h;
    }
  
  // Override area() for Rectangle
  virtual double area()  {
    return length * height;
  }
};

We are going to expose these functions to R using Rcpp Modules. See Intro to Rcpp for a refresher on Rcpp Modules.

RCPP_MODULE(shape) {
  Rcpp::class_<Shape>("Shape")
  .method("area", &Shape::area);
  
  Rcpp::class_<Circle>("Circle")
    .derives<Shape>("Shape")
    .constructor<double>();
  
  Rcpp::class_<Rectangle>("Rectangle")
    .derives<Shape>("Shape")
    .constructor<double, double>();
}

Now we can load the module using Rcpp::loadModule() where the second argument is TRUE so that all objects are loaded into the R environment. The ‘new’ functions creates a new instance of the class.

library(Rcpp)
sourceCpp("../src/my_polymorphism.cpp")
loadModule("shape", TRUE)
c <- new(Circle, 2)
r <- new(Rectangle, 3, 4)

c$area()
## [1] 12.56637
r$area()
## [1] 12