【问题标题】:Object composition in C++C++ 中的对象组合
【发布时间】:2020-05-15 07:06:11
【问题描述】:

我正在尝试在 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 知道,这才是最重要的。但是,此代码无效且无法编译。我怎样才能写出有效的东西来达到同样的效果?

【问题讨论】:

  • 您是否尝试了一个常见的Container 基类,并使用虚拟继承?
  • 用CRTP怎么样?
  • 模板类如何存储对容器对象的引用?

标签: c++ data-structures abstract-class object-composition


【解决方案1】:

尽管 Pusher 和 Popper 不知道元素,但 Stack 知道,这才是最重要的。

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

一种方法是使用虚拟继承,并定义一个抽象基类(例如称为Store),该基类提供对PusherPopper 通过实现的虚拟函数操作的存储的访问在Stack

但是,this approach also has numerous problems 通常在 C++ 中避免使用。更惯用的方法是使用the Curiously recurring template pattern (CRTP)

将您的 PusherPopper 更改为以 Stack 类作为模板参数的类模板:

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,是将PusherPopper 实现为decorators:也就是说,它们继承自Stack,而不是相反。这可能很有用,但前提是您更改名称:显然拥有一个既不执行推送也不执行弹出的类Stack 是没有意义的。再次,查看std::stack 适配器类的接口以获得灵感。

【讨论】:

  • > 你的建议有很多缺点,因此是不允许的。你能解释一下缺点是什么吗?
  • @Logorrhea 解析器必须接受无效代码,因为如果正确使用类,代码可能变为有效。但是,例如,如果用户创建了一个Pusher 类型的对象呢?那将是不正确的。为了提供更好的诊断,编译器禁止Pusher定义,而不仅仅是它的使用。
  • 如果在 C++ 中有一个类似于 C# 的抽象类的语言特性会很好,它会阻止你实例化它们。
  • 感谢您的回答。干杯。
  • @RobertAndrzejuk 不知道你想说什么……
猜你喜欢
  • 1970-01-01
  • 2012-05-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-05-31
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多