Are there any established patterns for checking class invariants in C++?
Ideally, the invariants would be automatically checked at the beginning and at the end of each public member function. As far as I know, C with classes provided special before
and after
member functions, but unfortunately, design by contract wasn't quite popular at the time and nobody except Bjarne used that feature, so he removed it.
Of course, manually inserting check_invariants()
calls at the beginning and at the end of each public member function is tedious and error-prone. Since RAII is the weapon of choice to deal with exceptions, I came up with the following scheme of defining an invariance checker as the first local variable, and that invariance checker checks the invariants both at construction and destruction time:
template <typename T>
class invariants_checker
{
const T* p;
public:
invariants_checker(const T* p) : p(p)
{
p->check_invariants();
}
~invariants_checker()
{
p->check_invariants();
}
};
void Foo::bar()
{
// class invariants checked by construction of _
invariants_checker<Foo> _(this);
// ... mutate the object
// class invariants checked by destruction of _
}
Question #0: I suppose there is no way to declare an unnamed local variable? :)
We would still have to call check_invariants()
manually at the end of the Foo
constructor and at the beginning of the Foo
destructor. However, many constructor bodies and destructor bodies are empty. In that case, could we use an invariants_checker
as the last member?
#include <string>
#include <stdexcept>
class Foo
{
std::string str;
std::string::size_type cached_length;
invariants_checker<Foo> _;
public:
Foo(const std::string& str)
: str(str), cached_length(str.length()), _(this) {}
void check_invariants() const
{
if (str.length() != cached_length)
throw std::logic_error("wrong cached length");
}
// ...
};
Question #1: Is it valid to pass this
to the invariants_checker
constructor which immediately calls check_invariants
via that pointer, even though the Foo
object is still under construction?
Question #2: Do you see any other problems with this approach? Can you improve it?
Question #3: Is this approach new or well-known? Are there better solutions available?
this
in initializer list. You could use it however in the body of the constructor. – Ventthis
here), but you cannot dereference it until the initialization list has completed the execution. On the other hand, there is no particular problem in calling a virtual function from the constructor, it will call thebase
overrider and not the final overrider, but that will not in itself be a problem, unless it is a pure virtual method in which case it will callterminate()
. – Endodermis