Как документировать/утверждать, когда наследование вызывает предварительное условие только для некоторых конечных типов

Рассмотрим этот простой базовый класс Foo, имеющий функцию foo, вызывающую чистую виртуальную функцию foo_, задокументированную с помощью Doxygen:

class Foo
{
  public:
    /** \brief This function logs x and does the job */
    void foo(double x);
  protected:
    /** \brief This function does the job */
    virtual void foo_(double x) = 0;
};

void Foo::foo(double x)
{
  std::clog << "'Foo::foo(double x)' is called with x = " << x << std::endl;
  this->foo_(x);
}

У меня нет предварительных условий для документирования этого абстрактного класса.

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

class Bar : public Foo
{
  public:
    /**
     * \brief This function does the job
     * \pre   x must be greater or equal to 0
     */
    virtual void foo_(double x);
};

void Bar::foo_(double x)
{
  assert(x >= 0.0 && "PRE: x can't be negative");
  // Do the job
}

Теперь у меня есть предварительное условие для x, когда я вызываю foo_, который вызывается foo. Затем у меня есть предварительное условие для foo в зависимости от конечного типа.

Некоторые вопросы :

  1. Должен ли я добавить предварительное условие в Foo::foo независимо от окончательного типа? Это выглядит логично, если пользователь никогда не знает окончательный тип, когда он использует класс. Но пользователь также может иметь другой класс Baz, производный от Foo без каких-либо предварительных условий, и явно вызывать Baz::foo(double) с отрицательными значениями. Это не должно быть проблемой.
  2. В моем представлении о полиморфизме класс Foo не должен ничего знать о своих потомках, тогда предусловия быть не может. Но пользователю материнского класса не обязательно знать дочерние элементы, чтобы использовать этот класс. Как разрешить это противоречие?
  3. Есть ли особый (/лучший) способ задокументировать такие вещи с помощью Doxygen?

person Caduchon    schedule 07.06.2016    source источник
comment
Предположим, что ваш базовый класс представляет собой что-то вроде формулы, и вы получаете два класса, реализующих, скажем, cos и sqrt, где первый может обрабатывать все аргументы, а второй — только положительные. Тогда не имеет смысла ограничивать аргумент базовым классом. С другой стороны, производный класс, который не может обрабатывать некоторые значения, не должен вызывать сбой программы, поэтому я бы не использовал assert, а использовал более изящную обработку ошибок.   -  person Karsten Koop    schedule 07.06.2016


Ответы (1)


Общее правило использования контрактов при наличии наследования:

  1. Предпосылки могут только ослабевать у потомков.
  2. Постусловия могут только усиливаться в потомках.

Это гарантирует, что клиент класса-предка не будет затронут какой-либо реализацией, предоставленной потомком, потому что этот клиент не обязательно должен знать о существовании такого потомка, и он должен иметь свободный доступ к членам, объявленным в классе-предке, даже если во время выполнения соответствующее выражение динамически привязывается к объекту класса-потомка.

Это отлично работает в большинстве ситуаций. Но в вашем примере класс-предок имеет предварительное условие true, а класс-потомок имеет более сильное предварительное условие. Есть несколько подходов к решению этой проблемы:

  1. Поднимите более сильное предварительное условие до класса-предка. Даже если реализация там имеет дело со всеми возможными случаями, в некоторых сценариях может ожидаться нетривиальное предварительное условие, и клиентам приходится иметь дело с этим.
  2. Измените реализацию класса-потомка, чтобы он обрабатывал все случаи, и полностью удалите более сильное предварительное условие.
  3. Избегайте повторного объявления рассматриваемого члена либо путем добавления нового метода со строгим предварительным условием в класс-предок, либо используя другой метод в классе-потомке, чтобы он не имел ничего общего с исходным.
  4. Используйте абстрактные контракты. Вместо прямого указания контракта это может быть вызов виртуальной функции, определенной в классе-предке. В вашем примере функция вернет true. Класс-потомок может переопределить эту функцию и добавить необходимые проверки. Главный недостаток этого решения заключается в том, что клиенту придется проверять абстрактное предварительное условие перед вызовом метода, чтобы убедиться, что он его не нарушает.

Окончательное решение зависит от конкретного сценария.

person Alexander Kogtenkov    schedule 08.06.2016
comment
Хороший обзор. Это заставляет меня думать, что проблема больше находится в самом предварительном условии. Мне очень нравится 4-й вариант. Спасибо ! - person Caduchon; 08.06.2016