0

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?

Logorrhea
  • 38
  • 5

1 Answers1

1

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.

Konrad Rudolph
  • 530,221
  • 131
  • 937
  • 1,214
  • > What you’re suggesting has numerous drawbacks and is thus not permitted. Could you explain what the drawbacks are? – Logorrhea Jan 29 '20 at 20:39
  • @Logorrhea The parser has to accept invalid code because the code *might* become valid if the class is correctly used. But what if, for example, the user then creates an object of type `Pusher`? That would be ill-formed. To provide better diagnostics, the compiler forbids the *definition* of `Pusher`, not just its use. – Konrad Rudolph Jan 30 '20 at 08:14
  • It would be nice if there was a language feature similar to C#'s abstract classes in C++, which prevent you from instantiating them. – Logorrhea Jan 30 '20 at 15:53
  • Thank you for the answer. Cheers. – Logorrhea Jan 30 '20 at 15:54
  • @Logorrhea C++ *has* abstract classes, and they work very similar to C#’s. But C# abstract classes don’t allow this either. – Konrad Rudolph Jan 30 '20 at 18:36
  • 1
    @RobertAndrzejuk Not sure what you want to say with that comment … – Konrad Rudolph Feb 03 '20 at 18:32
  • @Logorrhea how to make an abstract class in C++ : https://stackoverflow.com/questions/12854778/abstract-class-vs-interface-in-c – Robert Andrzejuk Feb 03 '20 at 18:41