Композиция объектов в C ++ - PullRequest
0 голосов
/ 29 января 2020

Я пытаюсь реализовать определенный вид композиции объектов в C ++. Идея состоит в том, чтобы создать набор всех структур данных, которые могут быть созданы путем составления абстрактных классов, которые знают только базовые c операции структуры данных. Например, стек был бы создан путем составления толкателя с поппером, очередь была бы создана путем составления очереди с поппером, и т. Д. c.

Проблема в том, что, хотя Pusher, Popper и Queuer служат только как абстрактные классы, и как таковые никогда не предназначены для создания экземпляров, они должны знать о том, как структура данных хранит данные внутри.

Цель состоит в том, чтобы иметь агности c абстрактных классов, которые предназначены только для передачи определений методов в конкретные классы структуры данных, следующим образом:

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

Вы можете видеть что хотя Пушер и Поппер не знают элементов, Стэк знает, и это все, что важно. Однако этот код недействителен и не будет компилироваться. Как я могу написать что-то действительное с таким же эффектом?

1 Ответ

1 голос
/ 29 января 2020

, хотя Пушер и Поппер не знают об элементах, Стэк знает, и это все, что важно.

Нет. Это не все, что важно для C ++, как вы можете ясно видеть, - и для этого есть все основания. То, что вы предлагаете, имеет многочисленные недостатки и поэтому не допускается. Однако существует несколько способов обойти это ограничение.

Одним из способов является использование виртуального наследования и определение абстрактного базового класса (называемого, например, Store), который обеспечивает доступ к хранилищу, которое оба Pusher и Popper работают через виртуальную функцию, которая реализована в Stack.

Однако, этот подход также имеет многочисленные проблемы и, как правило, избегается в C ++. Более идиоматический c подход использует Любопытно повторяющийся шаблон (CRTP) .

Измените Pusher и Popper на шаблоны классов, которые принимают 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_;
};

Нет необходимости говорить, что это все еще довольно запутанно, потому что ваша зависимость от данных инвертирована. Тщательно продумайте, какие зависимости вам нужны для ваших характеристик и как они полезны.

Еще одна реализация, близкая к 1032 * контейнерному адаптеру стандартной библиотеки, предназначена для реализации. Pusher и Popper как декораторы : то есть они наследуются от Stack, а не наоборот. Это может быть полезно, но только если вы измените имена: очевидно, что иметь класс Stack, который не выполняет ни push, ни popping, не имеет смысла. Снова посмотрите на интерфейс класса адаптера std::stack для вдохновения.

...