如何 document/assert 当继承仅对某些最终类型产生先决条件时
How to document/assert when inheritance induces a precondition for some final types only
考虑这个简单的基础 class 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);
}
我没有先决条件来记录这个摘要 class。
现在,考虑一个派生的 class 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
}
现在,当我调用 foo_
时,我在 x 上有一个前提条件,它由 foo
调用。然后,根据最终类型,我在 foo
上有一个前提条件。
一些问题:
- 我是否应该在
Foo::foo
中添加前提条件而不考虑最终类型?如果用户在使用 class 时永远不知道最终类型,这看起来合乎逻辑。但是用户也可以在没有任何前提条件的情况下从 Foo
派生另一个 class Baz
,并使用负值显式调用 Baz::foo(double)
。应该不是问题。
- 在我的多态性概念中,class
Foo
不需要知道他的 children 的任何信息,那么前置条件就不可能存在。但是母亲 class 的用户不必知道 children 就可以使用 class。如何解决这个矛盾?
- 有没有特定的(/最好的)方法来用 Doxygen 记录这种事情?
在存在继承的情况下使用合同的一般规则是:
- 先决条件只能在后代中变弱。
- 后置条件只会在后代中变得更强。
这保证了祖先 class 的客户端不受后代提供的任何实现的影响,因为该客户端不需要知道这样的后代的存在并且它应该能够访问成员在祖先 class 中自由声明,即使在 运行 时对应的表达式动态绑定到后代 class.
的对象
这在大多数情况下都能正常工作。但是在你的例子中,祖先 class 有一个先决条件 true
而后代 class 有一个更强的先决条件。有几种方法可以解决这个问题:
- 将更强的前提条件提升到祖先class。即使那里的实现处理了所有可能的情况,某些场景可能需要一个不平凡的先决条件,而客户必须处理那个。
- 更改后代 class 的实现,以便它处理所有情况并完全删除更强的前提条件。
- 通过向祖先 class 添加具有强前提条件的新方法或在后代 class 中使用不同的方法,避免重新声明有问题的成员,这样它就什么都没有了与原来的做。
- 使用抽象合同。它可以是对祖先class中定义的虚函数的调用,而不是直接指定契约。在您的示例中,该函数将 return
true
。一个后代 class 可以重新定义这个函数并添加它需要的检查。这个解决方案的主要缺点是客户端在调用方法之前必须检查抽象前提条件以确保它没有违反它。
最终决定取决于具体情况。
考虑这个简单的基础 class 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);
}
我没有先决条件来记录这个摘要 class。
现在,考虑一个派生的 class 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
}
现在,当我调用 foo_
时,我在 x 上有一个前提条件,它由 foo
调用。然后,根据最终类型,我在 foo
上有一个前提条件。
一些问题:
- 我是否应该在
Foo::foo
中添加前提条件而不考虑最终类型?如果用户在使用 class 时永远不知道最终类型,这看起来合乎逻辑。但是用户也可以在没有任何前提条件的情况下从Foo
派生另一个 classBaz
,并使用负值显式调用Baz::foo(double)
。应该不是问题。 - 在我的多态性概念中,class
Foo
不需要知道他的 children 的任何信息,那么前置条件就不可能存在。但是母亲 class 的用户不必知道 children 就可以使用 class。如何解决这个矛盾? - 有没有特定的(/最好的)方法来用 Doxygen 记录这种事情?
在存在继承的情况下使用合同的一般规则是:
- 先决条件只能在后代中变弱。
- 后置条件只会在后代中变得更强。
这保证了祖先 class 的客户端不受后代提供的任何实现的影响,因为该客户端不需要知道这样的后代的存在并且它应该能够访问成员在祖先 class 中自由声明,即使在 运行 时对应的表达式动态绑定到后代 class.
的对象这在大多数情况下都能正常工作。但是在你的例子中,祖先 class 有一个先决条件 true
而后代 class 有一个更强的先决条件。有几种方法可以解决这个问题:
- 将更强的前提条件提升到祖先class。即使那里的实现处理了所有可能的情况,某些场景可能需要一个不平凡的先决条件,而客户必须处理那个。
- 更改后代 class 的实现,以便它处理所有情况并完全删除更强的前提条件。
- 通过向祖先 class 添加具有强前提条件的新方法或在后代 class 中使用不同的方法,避免重新声明有问题的成员,这样它就什么都没有了与原来的做。
- 使用抽象合同。它可以是对祖先class中定义的虚函数的调用,而不是直接指定契约。在您的示例中,该函数将 return
true
。一个后代 class 可以重新定义这个函数并添加它需要的检查。这个解决方案的主要缺点是客户端在调用方法之前必须检查抽象前提条件以确保它没有违反它。
最终决定取决于具体情况。