C++ 中的对象组合

Object composition in C++

我正在尝试用 C++ 实现某种对象组合。这个想法是创建一组所有数据结构,这些数据结构可以通过组合只知道基本数据结构操作的抽象类来创建。例如,一个栈将由一个 pusher 和一个 popper 组成,一个队列将由一个 queuer 和一个 popper 组成,等等

问题在于,尽管 Pusher、Popper 和 Queuer 仅用作抽象 类,因此永远不会被实例化,但它们必须知道数据结构如何在内部存储数据。

目标是拥有不可知的抽象 类,仅用于将方法定义传递给具体的数据结构 类,方法如下:

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;
}

你可以看到,尽管 Pusher 和 Popper 不知道元素,但 Stack 知道,这才是最重要的。但是,此代码无效且无法编译。我怎样才能写出同样有效的东西?

even though Pusher and Popper don't know of elements, Stack does, and that's all that's important.

没有。就 C++ 而言, 并非 所有重要的东西,正如您可以清楚地看到的那样 — 并且有充分的理由。你的建议有很多缺点,因此是不允许的。但是,有多种方法可以解决此限制。

一种方法是使用虚拟继承,并定义一个抽象基 class(称为 Store),它提供对 PusherPopper 通过在 Stack.

中实现的虚函数进行操作

然而,this approach also has numerous problems and 在 C++ 中通常被避免。更惯用的方法是使用 奇怪的重复模板模式 (CRTP).

PusherPopper 更改为 class 模板,将 Stack class 作为模板参数:

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_;
};

这个不用说还是比较绕的,因为你的数据依赖倒过来了。仔细考虑您的特征需要哪些依赖项,以及它们有何用处。

另一个实现,并且接近标准库的 std::stack container adapter, is to implement Pusher and Popper as decorators:也就是说,它们继承自 Stack,而不是相反。这可能很有用,但前提是您更改名称:显然有一个既不执行推送也不执行弹出的 class Stack 是没有意义的。再次查看 std::stack 适配器 class 的接口以获得灵感。