Sponsored Link •
|
Summary
Assuring invariants can be a tricky endeavour in C++. Here are some techniques included a simple pointer class, which can help make life easier.
Advertisement
|
Let's consider the case of a simple OddNumber class. The invariant is relatively straightforward, the class must always contain an odd number. Here is a simple implementation for demonstration purposes:
class OddNumber1 { public: OddNumber1() : m(1) { } int GetValue() const { return m; } int SetValue(int x) { return m = x; } int AddValue(int x) { return m += x; } int ThrowException() { throw std::runtime_error("boom"); return 0; } void CheckInvariant() const { if (m % 2 != 1) { std::cerr << "invariant violated\n"; } } private: int m; };In order to assure the invariant through all control paths, the most obvious way would be as follows:
class OddNumber2 { public: OddNumber2() : m(1) { CheckInvariant(); } int GetValue() { CheckInvariant(); return m; } int SetValue(int x) { CheckInvariant(); m = x; CheckInvariant(); return m; } int AddValue(int x) { CheckInvariant(); m += x; CheckInvariant(); return m; } int ThrowException() { CheckInvariant(); int ret; try { throw std::runtime_error("boom"); return 0; } catch(...) { CheckInvariant(); throw; } CheckInvariant(); return ret; } void CheckInvariant() const { if (m % 2 != 1) { std::cerr << "invariant violated\n"; } } private: int m; };The problem with this approach is that it requires a significant amount of extra code and rewriting of the various functions. A more sophisticated solution is to use an invariant checking class which uses the well-known -- and rather bizarrely named -- RAII (Resource Acquisition Is Initialiation) idiom.
template<typename T> class InvariantChecker { public: InvariantChecker(const T* x) : m(x) { m->CheckInvariant(); } ~InvariantChecker() { m->CheckInvariant(); } private: const T* m; }; class OddNumber3 { typedef InvariantChecker<OddNumber3> guard; public: OddNumber3() : m(1) { guard g(this); } int GetValue() { guard g(this); return m; } int SetValue(int x) { guard g(this); return m = x; } int AddValue(int x) { guard g(this); return m += x; } int ThrowException() { guard g(this); throw std::runtime_error("boom"); return 0; } void CheckInvariant() const { if (m % 2 != 1) { std::cerr << "invariant violated\n"; } } private: int m; };This significantly cuts down on the extra code and complexity needed to assure the invariant. If we don't mind adding an extra level of indirection however, an even more elegant solution can be achieved by overloading the indirection operator->().
template<typename T> class GuardedPtr { public: struct Wrapper { Wrapper(T* x) : m(x) { m->CheckInvariant(); } T* operator->() { return m; } const T* operator->() const { return m; } ~Wrapper() { m->CheckInvariant(); } private: T* m; }; public: GuardedPtr(T* x) : m(x) { } Wrapper operator->() { return m; } const Wrapper operator->() const { return m; } private: T* m; };You can use this like you would any normal pointer, but whenever you call a member function through the operator->() the invariant is checked before and after the call. For example:
OddNumber1 o; GuardedPtr<OddNumber1> p(&o); p->SetValue(3); p->GetValue(); p->AddValue(1); try { p->ThrowException(); } catch(...) { }This code will notify us of three invariant violations. I'll leave it as an exercise to the reader to figure out where.
Have an opinion? Readers have already posted 11 comments about this weblog entry. Why not add yours?
If you'd like to be notified whenever Christopher Diggins adds a new entry to his weblog, subscribe to his RSS feed.
Christopher Diggins is a software developer and freelance writer. Christopher loves programming, but is eternally frustrated by the shortcomings of modern programming languages. As would any reasonable person in his shoes, he decided to quit his day job to write his own ( www.heron-language.com ). Christopher is the co-author of the C++ Cookbook from O'Reilly. Christopher can be reached through his home page at www.cdiggins.com. |
Sponsored Links
|