Consider this simple base class Foo
having a function foo
calling a pure virtual function foo_
, documented with 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);
}
I have no precondition to document for this abstract class.
Now, consider a derived class Bar
on which a precondition exists to work correctly :
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
}
Now, I have a precondition on x when I call foo_
, which is called by foo
. Then, I have a precondition on foo
depending on the final type.
Some questions :
Foo::foo
regardless of the final type ? It looks logical if the user never knows the final type when he uses the class. But the user can also have another class Baz
derived from Foo
without any precondition, and call explicitly Baz::foo(double)
with negative values. It shouldn't be a matter.Foo
doesn't have to know anything about his children, then the precondition can't be there. But the user of the mother class doesn't have to know the children to use the class. How to solve this contradiction ?The general rule of using contracts in presence of inheritance is:
This guarantees that a client of an ancestor class is not affected by any implementation provided by a descendant, because this client does not necessary know about existence of such a descendant and it should be able to access members declared in the ancestor class freely even if at run-time the corresponding expression is dynamically bound to an object of the descendant class.
This works fine in most situations. But in your example the ancestor class has a precondition true
and the descendant class has a stronger precondition. There are several approaches to deal with that:
true
. A descendant class can redefine this function and add the checks it needs to. The main drawback of this solution is that a client will have to check the abstract precondition before it calls the method to make sure it does not violate it.The final decision depends on the concrete scenario.