Friday, 17 May 2019

Copying polymorphic C++ objects using an inherited dup() method

In order to copy polymorphic objects in C++, it can be convenient to equip the base and derived classes with a .dup() method (or .clone() method) that returns a pointer to a copy of the (derived) object. When you have a large amount of different derived classes, overriding the base class's .dup() method for each of them can be a bit of a nuisance. In order to solve this, I sometimes use an "intermediate" class template that can be inherited instead of the base class to provide the .dup() method. This solution is not perfect, because it does not provide the possibility of covariant return types.

The class template Cloneable is defined as follows:
#ifndef CLONEABLE_HPP_
#define CLONEABLE_HPP_
/* a class template that can be inherited by a Derived class
* in stead of the Base class, such that the Derived class
* automatically implements the correct dup() method
*/
template<class Base, class Derived>
class Cloneable : public Base {
public:
virtual ~Cloneable() { /* empty */ }
virtual Base* dup() const override {
return new Derived(*static_cast<const Derived*>(this));
}
};
#endif
view raw cloneable.hpp hosted with ❤ by GitHub

In the following code snippet, the use of the Cloneable is demonstrated:
/* Example demonstrating the Cloneable class template,
* which is defined in cloneable.hpp
* compile this file with e.g.:
* g++ --std=c++11 example.cpp -o example
*/
#include <iostream>
#include <list>
#include <cmath>
#include "cloneable.hpp"
// an abstract base class for a binary operation
class BinaryOp {
public:
virtual ~BinaryOp() { /* empty */ }
// a binary operation implements operator() taking 2 arguments
virtual double operator()(double x, double y) const = 0;
// the base class MUST declare the dup() method
virtual BinaryOp* dup() const = 0;
};
// some binary operations that (indirectly) inherit BinaryOp
class Sum : public Cloneable<BinaryOp, Sum> {
public:
double operator()(double x, double y) const override {
return x + y;
}
};
class Product : public Cloneable<BinaryOp, Product> {
public:
double operator()(double x, double y) const override {
return x * y;
}
};
class Hypotenuse : public Cloneable<BinaryOp, Hypotenuse> {
public:
double operator()(double x, double y) const override {
return sqrt(x*x + y*y); // or use the hypot function from <cmath>
}
};
int main() {
// make a list with 3 binary operations
std::list<BinaryOp*> ops = {new Sum, new Product, new Hypotenuse};
// define some arbitrary x and y
double x = 3; double y = 4;
// evaluate the binary operations
for ( auto op : ops ) {
std::cout << (*op)(x, y) << std::endl;
}
// make a copy of the obs list using the dup method
std::list<BinaryOp*> ops_copy;
for ( auto op : ops ) {
ops_copy.push_back(op->dup());
}
// evaluate the copied operations
for ( auto op : ops_copy ) {
std::cout << (*op)(x, y) << std::endl;
}
// clean up memory
for ( auto op : ops ) delete op;
for ( auto op : ops_copy ) delete op;
return 0;
}
view raw example.cpp hosted with ❤ by GitHub
If someone has a better way to do this, let me know.

No comments:

Post a Comment