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?
even though Pusher and Popper don't know of elements, Stack does, and that's all that's important.
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 both Pusher
and Popper
operate on via a virtual function that is implemented in Stack
.
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 Pusher
and Popper
to class templates which take the Stack
class as template argument:
template <typename T>
class Pusher {
public:
void Push(int value) {
// Simulates internal logic of pushing a value.
T::elements(*this).push_back(value);
}
};
template <typename T>
class Popper {
public:
int Pop() {
// Simulates internal logic of popping a value.
int popped_value = T::elements(*this).back();
T::elements(*this).pop_back();
return popped_value;
}
};
class Stack: public Pusher<Stack>, public Popper<Stack> {
public:
template <typename T>
static std::vector<int>& elements(T& s) {
return static_cast<Stack&>(s).elements_;
}
private:
std::vector<int> elements_;
};
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::stack
container adapter, is to implement Pusher
and Popper
as decorators: that is, they inherit from Stack
rather than the other way round. This can be useful, but only if you change the names: obviously having a class Stack
that performs neither pushing nor popping makes no sense. Again, look at the interface of the std::stack
adapter class for inspiration.