Reputation: 1365
We have a large software framework where, in the heavy-lifting part of the code, we have an abstract base class, say
class Base {
public:
virtual double eval() const = 0;
};
which pretty much acts as a common interface class for many other classes spread all over various sub-packages, residing in different repositories and whatnot.
class Derived1 {
public:
virtual double eval() const override { return 1; }
}
However, there is really only one place where this function is called in our code, which is in the main loop of the core application.
int main(int argc, char* argv[]){
Base* x = createInstance(argv[1]); // some factory function
while(true){
std::cout << x->eval() << std::endl;
}
}
We recently discovered that it would be very useful to extend the interface such:
virtual double eval(int idx) const = 0;
Fixing the one call to this method in the core application is not the problem, but fixing the dozens of existing derived class implementations spread all over our code to accept and (in most cases) ignore this new parameter will be a nightmare.
I've thought about providing a legacy wrapper that would look like this:
double Base::eval(int idx) const { return this->eval(); }
But this would mean that all derived classes would need to implement the non-argument version of this function to exist, even though it's supposed to be deprecated. Alternatively, we could implement the non-argument version as a part of Base
, but then we would sacrifice the abstract nature of Base
, because then all components would be implemented already there.
Is there any "neat" way to solve this problem, or is the only sane thing to actually contact all the subpackage developers and make them change their implementations to adhere to the new interface?
Upvotes: 1
Views: 1027
Reputation: 115
You could do it like
class Base {
public:
virtual double eval() const{}
virtual double eval(int idx) const{
this->eval();
}
virtual ~Base() = 0;
};
Base::~Base(){}
and implement either the eval()
or eval(int idx)
in the derived classes.
The implemented pure virtual destructor is there to ensure that the Base
class cannot be instantiated. (see https://stackoverflow.com/a/14631710/2186392).
Upvotes: 1
Reputation: 6436
You could add a new abstract base class New
with double eval(int idx)
and deprecate the old one.
When you let one inherit the other, you can write new APIs accepting New
and they stay compatible with Old
.
struct New
{
virtual ~New() {}
virtual double eval(int idx) = 0;
};
struct Old : New
{
virtual double eval() = 0;
virtual double eval(int idx) { eval(); } // backwards-compatible
};
People implementing New
are forced to override the unary function, while people implementing Old
are forced to override the old, nullary one.
Upvotes: 6
Reputation: 7292
Alternatively, we could implement the non-argument version as a part of Base, but then we would sacrifice the abstract nature of Base, because then all components would be implemented already there.
You can either make one interface function as transition interface (so have both), or you can make two interfaces, in essence transitioning people to the second interface. That complicates your main loop, but does keep the classes actually abstract.
Upvotes: 1