Как обеспечить безопасность типов в двухмерной иерархии типов?

Рассмотрим следующую иерархию классов:

class A {};
class B : public A {};
class C : public A {};
class D : public A {};

Предположим, что он не является тривиально приводимым: B, C и D имеют по крайней мере одно попарно дизъюнктное определение члена и по крайней мере одно общее определение члена, унаследованное от A.

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

class AV : public std::vector<A*> {};
class BV : public AV {}; // contains only B*
class CV : public AV {}; // contains only C*
class DV : public AV {}; // contains only D*

Опять же, предположим, что он не является тривиально приводимым, как определено выше.

Предполагается, что эти классы являются «контейнерами с функциями»: предоставление общих функций контейнеров для их элементов и функций по линиям «get average of», «does any element satisfy» и т. д. . Операции над V всегда предполагают элементы класса для x в { A, B ,C, D }.

Теперь проблема заключается в том, что дочерние элементы AV предоставляют std::векторные функции, инстанцированные на A*, вместо их фактического содержания B*, C*, D* соответственно.

Есть три решения, которые я вижу, все с неудачными недостатками:

  1. переопределение элементов std:: vector в классах [B / C / D]V для приведения к правильному типу указателя
    • очевидный недостаток: много шаблонного кода
  2. пусть AV не наследует от std:: vector, а вместо этого [B|C|D]V наследует от соответствующего экземпляра std:: vector<[A / B / C]*>
    • очевидный недостаток: AV больше не может использоваться для кода, в котором используются только элементы класса A.
  3. не определяйте [A|B|C|D]V как иерархию классов, а как шаблон класса, предоставляйте реализации для параметра шаблона [A|B / C|D]
    • очевидный недостаток: потеря неявной информации об иерархии, esp. использование средств IDE, таких как Visual Studio — > код становится все труднее перемещаться

Обратите внимание, что проблема сохраняется, если контейнер был членом вместо родительского класса [A|B / C / D]V.

Есть ли лучшее решение проблемы? Если это идиоматично, как это называется?

Упрощение ради ясности:

  • C указатели вместо более подходящего std:: weak_pointer
  • короткие имена классов вместо того, чтобы рассказывать
  • Наследование от контейнера STL предоставляет функцию контейнера[s|ality] бесплатно, как и цель наследования.

Предоставление контейнера STL в качестве члена вместо этого приведет к реализации делегатов для большинства его функций в этом направлении:

class AV
{
public:
  auto begin() { return this->container.begin(); }
  auto end()   { return this->container.end(); }
  /* etc. pp. */
protected:
  std::vector<A*> container;
}

2 ответа

  1. Этого должно хватить.

    template<typename L>
    class  V : public std::vector<L*> {}; // all members of original AV
    class AV : public V<A> {}; // now empty
    class BV : public V<B> {}; // as before
    class CV : public V<C> {}; // "    "
    class DV : public V<D> {}; // "    "
    

    Если я не ошибаюсь, никакие дополнительные ограничения по сравнению с первоначальной настройкой не должны применяться.

    На данный момент мы находимся всего в одном шаге от оригинального комментария @SamVarshavchik, который, кстати, был самым первым действием по этому вопросу. Иногда самые простые ответы являются лучшими — и наиболее трудными для определения.

  2. АBV-это не ан AV. Там не должно быть наследования ,поскольку нет никаких полезных способов BVдля удовлетворения инвариантов разумно полезного изменяемогоAV. Наследование (C++) здесь вредно.

    Это старая проблема прямоугольника с квадратом, где неизменяемый квадрат является неизменяемым прямоугольником, но изменяемый квадрат не является изменяемым прямоугольником.

    Есть 3 зубца. Чтение, написание и реализация.

    An AV_readerявляется базовым классом a BV_reader(etc). Не мутирующие операции на a BVудовлетворяют условиям reasonabke и post aAV, предполагая, что то же самое верно Aи для and B.

    Для письма это не может быть так; a BV_writerне является разновидностью AV_writer. Возможно, это может быть перевернуто: anAV_writer-это своего рода BV_writer, но это снова опирается на Aи Bсвойства, и эти свойства немного менее распространены, чем инварианты чтения. Также некоторые расширения ( BV_writer_augment) выиграл; t быть частью AV_writer. Contravariance параметров может получить вас это бесплатно.

    На стороне реализации использование a vector<A*>для хранения a vector<B*>и обертывание его в мириады слепков innand out является ошибкой и болью.

    Вывод таков:

    Вы хотите ковариантное чтение, контравариантное письмо (я думаю, что я получил эти два имени правильно: я иногда переворачиваю их) и шаблон CRTP-подобной системы implementarion, которая делает проверку типа, хранящую фактический базовый вектор, не заставляя никаких приведений.

    Это не соответствует системе указателей полиморфного наследования C++ по умолчанию. Поэтому не используйте наследование полиморфизма указателей C++.

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