A simple scheme to implement Design by Contract in C++ - Luca Bolognese

A simple scheme to implement Design by Contract in C++

Luca -

☕ 5 min. read

Recently I got in­ter­ested in C++ again. The new lambda func­tions in C++ 11 open up a world of op­por­tu­ni­ties for C++ pro­gram­mers. I’ll talk more about how you can write func­tional code in C++ 11 in up­com­ing posts. For now let’s look at de­sign by con­tract.

Design by con­tract is a de­vel­op­ment style pro­moted by  Bertrand Meyer and it is im­ple­mented in his own Eiffel pro­gram­ming lan­guage. At core, it ad­vo­cates us­ing pre­con­di­tions, post­con­di­tions and in­vari­ants.

An in­vari­ant is an as­ser­tion that al­ways holds true for a class af­ter the class has been fully con­structed and if the code is not ex­e­cut­ing in­side a method. As a user of the class, you al­ways ob­serve the in­vari­ant to be true. As an im­ple­menter of the class, you can be as­sured that the in­vari­ant is true be­fore a method is en­tered and you need to make the in­vari­ant true again by the time your method ex­its.

A pre­con­di­tions is an as­ser­tion that needs to hold true at the start of a func­tion, for the post­con­di­tion to be true at the end of it. Taken to­gether, in­vari­ant, pre­con­di­tion and post­con­di­tion de­fine the con­tract be­tween the im­ple­menter and the user of a class.

Code for this post is here and here. Thanks to Andy Sawyer, Steve Bower and Ganesh Sittampalam for re­view­ing my code and sug­gest­ing im­prove­ments.

Preconditions are sim­ple and every­one uses them. They are those lit­tle if state­ments that you put at the start of your func­tions to make sure that the caller has given you the right pa­ra­me­ters.

double divide(double x, double y) {
    if(y == 0) throw new exception(“y cannot be 0”);
}

These lit­tle if’ state­ments don’t re­ally make the pre­con­di­tion stand out. They can be con­fused with other, un­re­lated, if’ state­ments that do com­pletely dif­fer­ent se­man­tic things. A more read­able al­ter­na­tive is:

double divide(double x, double y) {
    requires(y != 0);
    
}

Not an im­pres­sive dif­fer­ence, for sure, but kind of nice. The evil macro looks like this:

#ifndef ___PRECOND
#define requires(F) {if((!(F))) throw preexception(__FILE__, __LINE__,"Pre-condition failure: " #F);};
#else
#define requires(F)
#endif

Note that the ex­cep­tion main­tains in­for­ma­tion not just about the file and line num­ber of the fail­ure, but also a tex­tual rep­re­sen­ta­tion of the failed con­di­tion. Such things you can do with macro mag­ick.

Postconditions are trick­ier. In the case of a side-ef­fect free (pure) func­tion, a post­con­di­tion as­serts some­thing of in­ter­est about the re­turn value. In the case of a class, it as­serts some­thing of in­ter­est about the state of the class be­fore and af­ter the ex­e­cu­tion of the method.

Let’s start with a pure func­tion. I like to have all my as­ser­tion at the start of the func­tion to al­low rea­son­ing about it with­out look­ing at im­ple­men­ta­tion de­tails. But that poses the prob­lem that the re­sult is avail­able just at the end of the func­tion.  My so­lu­tion is to en­force this id­iom:

double divide(double x, double y) {
    double result;
    requires(y != 0);
    ensures(result < x); // Silly, just to falsify it in tests
return result; }

So you need to de­clare your re­sult up­front. That is the biggest lim­i­ta­tion of the over­all so­lu­tion in my opin­ion.  If that is ac­cept­able to you, the trick now is how to ex­e­cute the post­con­di­tion test be­fore the method ex­its. We can do that by stor­ing a lambda and ex­e­cut­ing it in the de­struc­tor:

typedef std::function<bool ()> ___dbcLambda;
class ___post {
public:
    ___post(const char *file, long line, const char *expr, const ___dbcLambda& postF)
        : _f(postF),
          _file(file),
          _line(line),
          _expr(expr)
    {}
    ~___post()
    {
        if( !std::uncaught_exception() && !_f() )
        {
            throw postexception(_file,_line,_expr);
        }
    }
private:
    const ___dbcLambda _f;
    const char * const _file;
    const long _line;
    const char * const _expr;
};

You might think that you should­n’t throw ex­cep­tions in a de­struc­tor. That is some­thing I never un­der­stood about the RAII pat­tern in C++. If I choose to use ex­cep­tions as my er­ror no­ti­fi­ca­tion method, how am I sup­posed to get no­ti­fied if there is a prob­lem re­leas­ing a re­source in RAII, other than by throw­ing an ex­cep­tion in the de­struc­tor?

Maybe be­cause of this, the stan­dard has an un­caugh­t_ex­cep­tion() func­tion that al­lows you to check if an ex­cep­tion has been thrown, so that you don’t throw an­other one dur­ing stack un­wind­ing. If you re­ally don’t like throw­ing in the de­struc­tor, feel free to as­sert.

You might be wor­ried about per­for­mance, but you re­ally should­n’t as you can dis­able all these macros in Release.

The macro then cre­ates a ___post class on the stack.

#define ensures(F) \
    int ___UNIQUE_LINE = __LINE__;  \
    auto ___UNIQUE_POST = ___post( __FILE__, __LINE__, "Post-condition failure:" #F, [&](){return (F);});

The UNIQUE stuff is messy busi­ness. Part of it is by de­sign and it is used to make sure that each __post vari­able has a unique name to have mul­ti­ple ensures’ in a func­tion. The other part is a workaround for this msvc bug. Let me know if you want more de­tails. I sus­pect there is a bet­ter way to do it.

Here is the full en­chi­lada …

#define ___MERGE(a, b) a##b
#define ___POST(a) ___MERGE(___postcond,a)
#define ___UNIQUE_POST ___POST(__LINE__)
#define ___LINE(a) ___MERGE(___line, a)
#define ___UNIQUE_LINE ___LINE(__LINE__)

The case in which a post­con­di­tion is used in­side a method of a class is even trick­ier be­cause the post­con­di­tion must be able to com­pare the state of the class at the en­trance of the method to the state of the class at its exit. Assuming a Counter ob­ject with an Add method and as­sum­ing ___pre’ cap­tures the state of the counter at the start of the method, you’d like to write some­thing like:

    void Add(int x) {
        ensuresClass(this->c_ == ___pre.c_ + x);
}

Now, this is tricky. The only way to cap­ture the old’ state in ___pre’ is by mak­ing a copy of it and store it there. This is what the code be­low does:

#define ensuresClass(F) \
    auto ___pre(*this); \
    auto ___UNIQUE_POST = ___post( __FILE__, __LINE__, "Post-condition failure: " #F, [&](){return (F);});

More trou­bling is the pos­si­bil­ity that the class does­n’t have a copy con­struc­tor. In that case you ex­plic­itly need to as­so­ci­ate a value with ___pre2’ by pass­ing it as the first pa­ra­me­ter to the ap­pro­pri­ate macro as in the code be­low:

    void Add(int x) {
        ensuresClass2(this->c_, c_ == ___pre2 + x);
    }

Which is im­ple­mented as fol­lows:

#define ensuresClass2(ASS,F) \
    auto ___pre2(ASS); \
    auto ___UNIQUE_POST = ___post( __FILE__, __LINE__, "Post-condition failure: " #ASS " is ___pre2 in " #F, [&](){return (F);});

And I know about the gi­ant ass …

Now for in­vari­ants. The user should im­ple­ment an is­Valid() method on his class as be­low:

    bool isValid() { return c_ >= 0;}

Then he should add an invariant()’ call at the start of each method, at the end of each con­struc­tor and at the start of each de­struc­tor:

    void Add(int x) {
        invariant();
        requires(x < 10);
        ensures(this->c_ == ___pre.c_ + x);
}

This calls the isValid’ func­tion at the start of the method and at the end of it us­ing the same de­struc­tor trick:

#define invariant() \
    if(!(this->isValid())) throw preexception(__FILE__, __LINE__,"Invariant failure"); \
    auto ___UNIQUE_INV = ___post( __FILE__, __LINE__, "Invariant failure", [&](){return this->isValid();});

All the above ma­chin­ery is not at all equiv­a­lent to hav­ing such con­structs in the lan­guage, but it is sim­ple enough and with a de­cent enough syn­tax to be in­ter­est­ing.

Now a caveat: I have no idea if any of this works. It does work in my ex­am­ples and its be­hav­iour seems rea­son­ably safe to me, but I haven’t tried it out on any big code­base and haven’t stressed it enough for me to be con­fi­dent rec­om­mend­ing its us­age. So, use it at your own risk, let me know how it goes.

11 Comments

Comments

Generally, I like the idea.
But you might want to consider what Herb Sutter has to say about this: http://www.gotw.ca/gotw/047...
Ben

Thanks for the link. It is hard to disagree with Herb, but I'll try :-)
He doesn't like using uncaught_exception in a destructor on practical and moral grounds.
1. On practical grounds, doing so is not composable in certain scenarios. Meaning that using RIIA objects that rely on it in destructors needs to be done carefully. That is true, but that doesn't disqualify the solution. Many things are useful, but need to be used carefully. In the case exposed in this blog, that is not a problem. The behavior would be correct.
2. On moral grounds, you often need to report errors differently if you are already in an error condition. I.E. you don't want to send e-mail to your user twice, you don't want to report the second (bogus) error in a parser, you don't want to allocate memory if you got an out-of-mem exception etc ...
More generally, I fail to see a better way to manage errors in releasing resources in RIIA. All the ones I've seen (i.e. logging to file) are gross hacks that don't compose at all.
But if you still are un-convinced by my argument, feel free to change the throwing code with an assert and things work pretty much the same.

Yes, I would definitely use an assertion, bypassing this whole problem.
But I wonder why you use exceptions at all? From my understanding (which might be wrong, as I have never read any books about dbc) a violated contract indicates a programmer's error (a bug), not an exceptional situation. Thus, if your post-condition is violated your data is very likely to be in an inconsistent state. How would you even react to such a dbc-exception being caught?
Ben.

If you leave them on in Release mode (which seems to be more frequently done for precondition), then you want to treat them as any other error (i.e. clean up resources, logging and reporting to user).
In Debug, it probably doesn't makes a difference. I believe that's true of exceptions in general.

You should distinguish carefully between runtime errors that lie outside of your program (occurring most often when acquiring or accessing a resource, e.g. a file can not be opened) and bugs (incorrect code, in this case contract violations).
The first case can and should be treated via exceptions. These can be handled at run-time. The latter can not be safely dealt with via exception handling, as your program is already in an inconsistent state. If unlucky, you might totally crash your program when throwing an exception as your destructors' preconditions might be invalid. If even unluckier, you might end up with a program whose data only gives the impression of being consistent.
My take on violated contracts in release mode would be to implement a functionality that is orthogonal to exception handling by providing an appropriate callback function. This callback function would then inform the user, log the error, try to write out any data that could help the user restore is work later on (which might already fail badly), possibly clean up and finally... call std::terminate().
Ben.

It is not possible to draw the line so sharply. What is a resource error for one user of your library, might be a coding error for another. That's why most programming environent (i.e. .NET, Java, PHP, ...) have an invalid argument exception that gets thrown when a precondition is violated.
You distinguish between the two cases by catching the different kinds of exception and doing something different about them. The catching is done outside of your library, which is the only place where you have enough information to really know what to do. Who knows, for the space shuttle, calling terminate() for a bug might not be the right action. You don't know what to do, the user of your library does.

I see your point.
Although I would try to treat the two error sources differently in production code, I agree that in library code it might not always be possible to make a clear distinction.
Thanks for the feedback.
Ben.

.... though having thought about it for a second I would tend to go for the callback approach instead of the exception path in library code: After all, you can still implement a callback handler which throws an exception. But it's not possible the other way around.

Frankly, I don't see the advantages of a callback approach compared to an exception approach. In both cases you let the user decide which code runs when an error occurs. Both approaches let's you differentiate between different categories of errors. The callback approach has the drawbacks of being significantly more cumbersome to use.
Most 'modern' libraries out there (.net, java, etc...) go for the exception approach. Most of the old style ones go for a 'returning an error code' approach. There are advantages and disadvantages in both approaches (topic for another post), but I don't see the need for a third one.
Good (?) thing about C++ is that it allows us vast latitude on how we do things ...

"In both cases you let the user decide which code runs when an error occurs. Both approaches let's you differentiate between different categories of errors."
That is exactly not the case. You have no idea what destructors will be called.
"...I don't see the need for a third one."
It's actually not a new approach. Think of is as enabling assertions for your release build (maybe the term callback was misleading). Also, when operator new() fails a similar approach is taken in calling an error handler, which can be changed using std::set_new_handler().
From my point of view exceptions signal error conditions that might be recoverable. Now while we can discuss about pre-conditions this is clearly not the case when your class invariant is broken or your post-condition is invalid. And then you're trying to fix bugs at runtime.
Ben.

Hi Ben,
Le'ts just agree to disagree here. I think we are running in circles.
In the end, it is trivial to plug whatever error reporting mechanism you feel is best in this framework.
Cheers,
.luca

0 Webmentions

These are webmentions via the IndieWeb and webmention.io.