0

考虑这个简单的基类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从另一个类派生,并使用负值显式调用。这不应该是一个问题。FooBaz::foo(double)
  2. 在我的多态性概念中,班级Foo不必知道他的孩子的任何事情,那么前提条件就不能存在。但是母类的用户不必知道子类就可以使用该类。如何解决这个矛盾?
  3. 有没有一种特定的(/最好的)方法来用 Doxygen 记录这种事情?
4

1 回答 1

1

在存在继承的情况下使用合约的一般规则是:

  1. 前提条件只能在后代中变得更弱。
  2. 后置条件只能在后代中变得更强。

这保证了祖先类的客户端不受后代提供的任何实现的影响,因为该客户端不需要知道这样的后代的存在,并且它应该能够自由访问在祖先类中声明的成员,即使在运行时相应的表达式被动态绑定到后代类的对象上。

这在大多数情况下都可以正常工作。但是在您的示例中,祖先类具有先决条件true,而后代类具有更强的先决条件。有几种方法可以解决这个问题:

  1. 将更强的前提条件提升到祖先类。即使那里的实现处理了所有可能的情况,某些场景可能需要一个重要的前提条件,客户必须处理它。
  2. 更改后代类的实现,以便它处理所有情况并完全删除更强的前提条件。
  3. 避免重新声明相关成员,方法是向祖先类添加具有强前提条件的新方法,或者在后代类中使用不同的方法,使其与原始方法无关。
  4. 使用抽象合约。它可以是对祖先类中定义的虚函数的调用,而不是直接指定合约。在您的示例中,该函数将返回true。后代类可以重新定义这个函数并添加它需要的检查。此解决方案的主要缺点是客户端必须在调用该方法之前检查抽象前提条件以确保它不违反它。

最终决定取决于具体情况。

于 2016-06-08T08:33:07.237 回答