Семантика значений C ++, неизменность и наследование - PullRequest
0 голосов
/ 26 мая 2020

Общий указатель на неизменяемый тип имеет семантику значения

Я пытаюсь создать класс построителя, который имеет семантику значений, которая выглядит примерно так

class Pipeline {
public:
  Pipeline(
     const std::string& name, 
     std::optional<size_t> limitIn,
     std::optional<size_t> limitOut) {...}

  shared_ptr<const Pipeline> limitInput(size limit) const { 
    return make_shared<Pipeline>(name_, size_, limit, limitOut_) ;
  }

  shared_ptr<const Pipeline> limitOutput(size limit) const { 
    return make_shared<Pipeline>(name_, size_, limitInput_, limit) ;
  }
private:
  const string name_;
  const size_t limitInput_;
  const size_t limitOutput_;
};

Поскольку переменные-члены являются константами, shared_ptr по сути неизменяема.

Однако этот шаблон не работает, когда мне нужно добавить наследование. Например:


class Pipeline {
 ...

 virtual void doSomething() const = 0;
}

Теперь в любом из методов (скажем, в limitOutput), когда мне нужно создать новый экземпляр Pipeline, мне также нужно будет знать о производном классе, поскольку я не могу создать экземпляр трубопровода больше. Я могу придумать один способ решить эту проблему - добавить еще один виртуальный метод для инициализации объекта.

class Pipeline {
 ...

 virtual shared_ptr<Pipeline> create(const std::string& name, 
     std::optional<size_t> limitIn,
     std::optional<size_t> limitOut) const = 0;
}

class SpecialPipeline  : public Pipeline {
 ...

 virtual shared_ptr<Pipeline> create(const std::string& name, 
     std::optional<size_t> limitIn,
     std::optional<size_t> limitOut) const override {
    return make_shared<SpecialPipeline>(name, limitIn, limitOut);
 }
};

Теперь все методы будут просто подчиняться этому

  shared_ptr<const Pipeline> limitInput(size limit) const { 
    return create(name_, size_, limit, limitOut_);
  }

Пока это работает, я лично считаю, что это не изящно, предполагает дублирование и не ощущается идиоматикой c. Как можно go об этом реализовать? Будем признательны за любые отзывы.

Ответы [ 2 ]

1 голос
/ 26 мая 2020

Самый простой способ справиться с этой проблемой:

  • Не раскрывать конструктор; сделайте его защищенным.
  • Предоставьте элементы фабрики для базового типа и производных типов, которые возвращают std::unique_ptr<const T> или std::shared_ptr<const T>.
  • Удалите квалификацию const для ваших элементов данных.
  • Добавьте виртуальный метод клонирования, который создает копию и возвращает std::unique_ptr<Pipeline>.

Извне получить неконстантный объект невозможно, поскольку конструктор не является publi c, поэтому члены не обязательно должны быть константными.

Поскольку члены не являются константами, ваши методы фабрики мутаций могут:

  • Вызвать метод clone для создания копии.
  • Мутировать элемент данных в копии.
  • Извлечь указатель из std::unique_ptr и вернуть новый интеллектуальный указатель с константной целью.
0 голосов
/ 26 мая 2020

Я бы использовал шаблон Любопытно повторяющийся шаблон здесь:

template <class T>
class Pipeline {
public:
  Pipeline(
     const std::string& name, 
     std::optional<size_t> limitIn,
     std::optional<size_t> limitOut) {...}

  shared_ptr<const T> limitInput(size limit) const { 
    return make_shared<T>(name_, limit, limitOut_) ;
  }

  shared_ptr<const T> limitOutput(size limit) const { 
    return make_shared<T>(name_, limitInput_, limit) ;
  }
  ...
};

И в дочерних классах:

class Child: public Pipeline<Child> {
public:

    Child(
     const std::string& name, 
     std::optional<size_t> limitIn,
     std::optional<size_t> limitOut): Pipeline<Child>(name, limitIn, limitOut) {}

    ...
];

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

...