I'm trying to implement a certain kind of object composition in C++. The idea is to create a set of all data structures which can be created by composing abstract classes that only know basic data structure operations. For example, a stack would be created by composing a pusher with a popper, a queue would be created by composing a queuer with a popper, etc.
The trouble is, even though Pusher, Popper and Queuer only serve as abstract classes, and as such are never meant to be instantiated, they have to know about how the data structure internally stores the data.
The goal is to have agnostic abstract classes that are only there to pass down method definitions to the concrete data structure classes, in the following fashion:
class Pusher {
public:
void Push(int value) {
// Simulates internal logic of pushing a value.
elements.push_back(value);
}
}
class Popper {
public:
int Pop() {
// Simulates internal logic of popping a value.
int popped_value = elements.back();
elements.pop_back();
return popped_value;
}
}
class Stack: public Pusher, public Popper {
private:
vector<int> elements;
}
You can see that even though Pusher and Popper don't know of elements, Stack does, and that's all that's important. However, this code isn't valid and won't compile. How can I write something valid to the same effect?
No. It’s not all that’s important as C++ is concerned, as you can clearly see — and with good reason. What you’re suggesting has numerous drawbacks and is thus not permitted. However, there are several ways to work around this limitation.
One way is to use virtual inheritance, and to define an abstract base class (called, e.g.
Store) that provides access to the storage that bothPusherandPopperoperate on via a virtual function that is implemented inStack.However, this approach also has numerous problems and is generally avoided in C++. A more idiomatic approach uses the Curiously recurring template pattern (CRTP).
Change your
PusherandPopperto class templates which take theStackclass as template argument:Needless to say this is still quite convoluted, because your data dependence is inverted. Think carefully about what dependencies you need for your traits, and how they are useful.
Yet another implementation, and one that’s close to the standard library’s
std::stackcontainer adapter, is to implementPusherandPopperas decorators: that is, they inherit fromStackrather than the other way round. This can be useful, but only if you change the names: obviously having a classStackthat performs neither pushing nor popping makes no sense. Again, look at the interface of thestd::stackadapter class for inspiration.