Skip to main content
GameDev.net gamedev.net
🔒 Locked

The C++ "finally" keyword and its relation to design-by-contract

Started by Jotaf Dec 20, 2008 at 5:12 PM 29 replies 8.6k views
Original Post
Jotaf
Jotaf
I've seen a few proposed systems to apply design-by-contract to C++. They're a bit verbose. It's obvious that common assertions at the beginning of a function are enough to express pre-conditions. Post-conditions, however, are much trickier. You can slap some assert()'s at the end of your function body, but you can't guarantee that a previous piece of code won't return earlier. Especially when re-visiting old code, you can just add a new return and not care/not remember that some post-conditions must always be verified. It's tedious work, and hard to maintain all those assertions. So there's a tight relationship between clean-up when exiting scope and asserting post-conditions. It would be cool if we could instruct the compiler to run some code on scope exit, no matter what happens. This would be solved with the proposed keyword "finally", which is available in VS but has to be used with a "try" block, making it very intrusive (ie, try {...} finally {...} ). It's also not available on other compilers (AFAIK). With this in mind, I set out to re-create the "finally" keyword myself. Using it in design-by-contract:

void f() {
	// pre-conditions:
	assert(...);
	// post-conditions:
	finally {
		assert(...);
		assert(...);
	}

	// main body
}
It's also useful for other kinds of clean-up, undoing actions on error, logging, etc. I managed to do it with a macro to create a local class that executes a block of code in its destructor. The main advantage of the macro is that you can access the scope of the original function, which a simple local class usually can't do. Without that feature the method would be rendered obsolete. It's a macro, and I know many people don't like those. But anyways, before posting the code I'd like to gauge interest in this. Would you find it useful? Is there a simpler alternative? Jotaf
ToohrVyk
ToohrVyk
Why not use a function ? This way, no matter where you return from, you still assert post-conditions:

private: void do_foo() { /* Code here */ }public: void foo() { assert(pre); do_foo(); assert(post); }


The problem with destructors is that you don't really want to check for post-conditions when you throw an exception: any conditions related to "I will return the right data" are obviously incorrect when an exception is thrown, and any conditions related to the class state are either incorrect (an exception happened because they couldn't become true) or obviously correct (the exception rolled back any modifications to restore the initial state).
Jotaf
Jotaf
Quick response! :)

That's a viable solution, but it suffers from the maintainability problem of other alternatives. You'll need to work hard to have a duplicate of every function in your program -- not to mention name clashes. It's something I didn't think of and I'm glad you mentioned it, attempting to streamline its use might be worth a shot.

I see where you're getting at with the destructors, but it doesn't seem an issue with other systems. To name a few:
Using Design by Contract in C by Charlie Mills
Design by Contract Framework for C++ By Kevin McFarlane
Design by Contract in C++ By Jarl Lindrud

The first one involves a pre-processing perl script, but the other two simply use local classes and destructors like I'm proposing. (Their methods don't allow such freedom to define any assertion you want, though.) They simply ignore the issue you raised. Of course, just because someone else is doing so is no excuse. But from my experience, it doesn't really matter if some code throws, thereby causing another one to throw; it's what happens commonly with a lot of errors anyway.
Lode
Lode
This could also be useful in C, to do the free-ing of allocated memory at the end of a function!
PinguinDude
PinguinDude
Or in other words, Digital Mars D has this feature too.

Still, it's a really nifty feature and it's too bad that g++ doesn't have it. ( Or atleast, not that I know of. And if this is the case, then it really lacks documentation on the net in my opinion. )
http://sourceforge.net/projects/pingux/ <-- you know you wanna see my 2D Engine which supports DirectX and OpenGL or insert your renderer here :)
iMalc
iMalc
Quote:
Original post by ToohrVyk
The problem with destructors is that you don't really want to check for post-conditions when you throw an exception: any conditions related to "I will return the right data" are obviously incorrect when an exception is thrown, and any conditions related to the class state are either incorrect (an exception happened because they couldn't become true) or obviously correct (the exception rolled back any modifications to restore the initial state).
The method of using a destructor isn't totally without merrit though. It can still be used to verify the internal state of the class upon exit, if you're aiming for writing methods with strong exception guarantees.
It will also work fine for methods that cannot throw exceptions.
MaulingMonkey
MaulingMonkey
Quote:
Original post by Jotaf
With this in mind, I set out to re-create the "finally" keyword myself.


Your "finally" has different semantics than the normal usage -- when execution reaches the finally block, it only registers actions to be done later, rather than occurring immediately. I'd name it something else. Maybe POSTCONDITIONS ? (lowercase #defines are evil! EVIL!)

The dangers of possibly throwing from a destructor have already been noted, yes?
Jotaf
Jotaf
Quote:
Original post by MaulingMonkey
It will also work fine for methods that cannot throw exceptions.


That's the idea. I'll probably have two versions. The first one is intended for cleaning-up code (Finally), and as you probably expect behaves exactly like a destructor. As the C++ Standard has it, it will terminate the process if a 2nd exception is found. Here's a nice discussion on the subject (and the reasoning behind throwing destructors).

The second version (PostConditions) only runs when an exception was not already thrown (by checking std::uncaught_exception()). So it's only intended to run in the event of a return statement. Here's where you can have some asserts() for your post-conditions. It's more general than that, can someone suggest a new name?


Quote:
Original post by MaulingMonkey
I'd name it something else. Maybe POSTCONDITIONS ? (lowercase #defines are evil! EVIL!)


Weird, I find that UPPERCASE in the middle of well-formed code hurts my eyes so if this is going to be of any general usage, it could be LikeThis. Of course it'd be better to leave it as a choice to the programmer-user. Not enough libraries give you that kind of choice :)


Quote:
The dangers of possibly throwing from a destructor have already been noted, yes?


Ah yes, the old problem with throwing destructors. I hope the addition of std::uncaught_exception() mentioned above is enough to avoid that, and the link might provide you with some insight on the whole subject.
Jotaf
Jotaf
Quote:
Original post by Lode
This could also be useful in C, to do the free-ing of allocated memory at the end of a function!


Unfortunately this uses local classes so it's not really for C. I see where you're coming from though.

Quote:
PinguinDude
Or in other words, Digital Mars D has this feature too.

Still, it's a really nifty feature and it's too bad that g++ doesn't have it. ( Or atleast, not that I know of. And if this is the case, then it really lacks documentation on the net in my opinion. )


It's too bad that it isn't in C++ already! It would allow for much better error handling and come handy in many other situations as well. Unfortunately C++ makes it really difficult to delay code execution of any kind. I've been looking at boost::lambda (and even saw a DBC implementation that used it) but it's not the same thing.
Rydinare
Rydinare
If you need to replicate a finally block as in Java, there's two approaches which can be useful in different circumstances.

1) Using ScopeGuard, which is part of the Loki library.
2) You can replectate try-finally in C++ using the following idiom:

try{   ...}catch (...){   finally_code;   throw;}finally_code;


You can encompass this with a macro like this:

#define MY_FINALLY(X) catch(...) {X; throw; } X; 


You can use it like this. Note the difference of using ( ) instead of { } for the "finally block". This will prevent you from being able to use , as a statement that isn't encompassed by nested parenthesis, but this has rarely been an issue in practice.

try{   ...}MY_FINALLY(   finally_code;)


Although I usually advocate avoiding macros as much as anyone, weighing that against code duplication is a no brainer.

One other thought on your specific question, though. If you think about, post conditions have automatically been violated if you throw an exception out of a function. So, personally, I would either avoid the asserts when an exception is thrown, or at best, simply use the asserts to guarantee one of the exception guarantees in this case, because some other expected post-conditions may not make sense in the presence of an exception.
Jotaf
Jotaf
Quote:
Original post by Rydinare
If you need to replicate a finally block as in Java, there's two approaches which can be useful in different circumstances.

1) Using ScopeGuard, which is part of the Loki library.


I've seen it, it's ok. But it suffers from being too verbose, and it limites you to extremely simple operations. For example:

void Decrement(int& x) {    --x;}void UseResource(int refCount) {    ++refCount;    ScopeGuard guard = MakeGuard(Decrement, refCount);    ...}


Instead of this:

void UseResource(int refCount) {    ++refCount;    Finally {        --refCount;    }    ...}


Can you see how theirs doesn't scale well with code complexity? You'll have to define a dummy function for every little operation you want to do. Otherwise you're stuck with a limited set of one-liners.

But yes, I respect ScopeGuard and if I didn't have a choice it's the one I'd probably use.

Quote:
2) You can replectate try-finally in C++ using the following idiom:

*** Source Snippet Removed ***


Unfortunately, you still need to duplicate "finally_code" at every return statement. It's a serious penalty for any sizable project.

Quote:
So, personally, I would either avoid the asserts when an exception is thrown...


That's possible with the standard function uncaught_exception() I mentioned.

I'll post back when I got this thing working, probably later today (I've got other stuff to do too :) ).
loufoque
loufoque
To be honest, I never found the point of adding postconditions.
Preconditions are useful because you can be provided with bad input, and so it might be useful to detect such input; but postconditions are only dependent on your own code, which should always be correct whatever the correct input.

Also, the reason why there is no finally keyword in C++ is because it is intended that you use destructors instead, which are way less verbose if you model your objects correctly.
Jotaf
Jotaf
Quote:
Original post by loufoque
but postconditions are only dependent on your own code, which should always be correct whatever the correct input.


So you can guarantee that all of your code returns correct output all of the time? You must be a hell of a programmer then ;)

Quote:
Also, the reason why there is no finally keyword in C++ is because it is intended that you use destructors instead, which are way less verbose if you model your objects correctly.


I can't see how the simple existence of well-defined objects prevents a function from returning bad output. Bad output is more than just ill-formed objects.

Don't mean to come out too harshly but my engineering background tells me that all systems fail and the more assumptions you make, the badder it will crash. A program being maintained is always subject to human error.
Rydinare
Rydinare
Quote:
Original post by Jotaf
Can you see how theirs doesn't scale well with code complexity? You'll have to define a dummy function for every little operation you want to do. Otherwise you're stuck with a limited set of one-liners.

But yes, I respect ScopeGuard and if I didn't have a choice it's the one I'd probably use.


I definitely agree. I usually only use ScopeGuard if I can reuse the extra function that goes in the ScopeGuard. Otherwise I opt for option #2 with the macro I mentioned earlier.

Quote:
Original post by Jotaf
Unfortunately, you still need to duplicate "finally_code" at every return statement. It's a serious penalty for any sizable project.


When you say serious penalty, what are you referring to? Code size? Performance? The macro I suggested removes the duplication from the maintenance point of view, although technically the code will exist twice. If it's an optimization related issue, you still have the option of moving that "finally_code" into another function if you find it's a performance bottleneck. Since you're intending to use asserts, that code actually won't exist in release anyway. If you're using just asserts, you could take it a step further and macro out the try-finally block as well. But, again, I do advocate being cautious of overuse of macros.
iMalc
iMalc
Quote:
Original post by Jotaf
... the more assumptions you make, the badder it will crash. A program being maintained is always subject to human error.
There's a flipside to that though. The fewer assumptions you make, the more code you have to write and the more code you have, the more bugs you have (on average). "Dammned if you do, damned if you don't", if you will. The best way is to document assumptions either in the form of a runtime assert or a compile-time C_ASSERT.

And yes human error is alive and kicking - keeps websites like www.thedailywtf.com alive.
loufoque
loufoque
Quote:
So you can guarantee that all of your code returns correct output all of the time? You must be a hell of a programmer then ;)

Yes, I can infer whether a simple constant-time relation is true after a page of code.
If I can't, it means the code must be rewritten.

Quote:
I can't see how the simple existence of well-defined objects prevents a function from returning bad output. Bad output is more than just ill-formed objects.

How is that even related to finally?
Finally is a mechanism to perform cleanup, be it in face of exceptions or not. The mechanism recommended to do that in C++ is destructors.
It does require modeling your objects so that there is a destructor that does what you want.

For example, instead of writing
void UseResource(int refCount) {    ++refCount;    try    {        ...    }    finally {        --refCount;    }}

You write
void UseResource(resource_ptr res) {    ...}
theOcelot
theOcelot
There's always goto. A couple goto's to a single "end" label doesn't strike me as too heinous.

As for my other off-hand idea: I think the term I'm looking for is a closure: a locally defined function that has access to the functions' local variables. You could define a closure that does all your cleanup and post-condition stuff. The only problem is that you can't return from the whole function that way. So you have your finally function, then return; everytime you want to kill the function. Two lines of code. Better IMHO than macro based evilness.

Can't you fake closures with local classes, or something like that?
ToohrVyk
ToohrVyk
Quote:
Original post by loufoque
To be honest, I never found the point of adding postconditions.
Preconditions are useful because you can be provided with bad input, and so it might be useful to detect such input; but postconditions are only dependent on your own code, which should always be correct whatever the correct input.


Preconditions and postconditions have different objectives. As you mentioned, preconditions are useful because they document and enforce the expectations and assumptions that a piece of code has over its environment. By contrast, postconditions document and enforce the assumptions that the environment has over the piece of code.

You can't determine whether your code is correct without knowing what it's expected to do. Of course, when you're writing a function, what your code should be doing is obvious because you've just designed the function. But a few months along the line, the only thing you have left is your code and your function name—enough to guess what the function does, but not accurately enough to make out which details of your code are accidental and which are actually expected to happen.
loufoque
loufoque
Quote:
Can't you fake closures with local classes, or something like that?

Closures in C++ are known are functors.
See Boost.Phoenix, Boost.Lambda, etc. for generating functors in line with a simple Domain-Specific Embedded Language.
Jotaf
Jotaf
Quote:
Original post by Rydinare
Quote:
Original post by Jotaf
Unfortunately, you still need to duplicate "finally_code" at every return statement. It's a serious penalty for any sizable project.


When you say serious penalty, what are you referring to? Code size? Performance?


Sorry, I wasn't complaining about either of those. I meant that since many functions have decision paths that return early, you'd need to duplicate that code at every return statement.

Quote:
Original post by loufoque
void UseResource(resource_ptr res) {
...
}


Yes but at the end of the day, are you really using that for every single aspect of your program? It's impractical.

Quote:
Closures in C++ are known are functors.
See Boost.Phoenix, Boost.Lambda, etc. for generating functors in line with a simple Domain-Specific Embedded Language.


That one I'll buy. It's been done before, with a reported "noticeable increase in compilation time" and "The runtime penalty is also significant". We need to aim for a more lightweight solution.

Quote:
Original post by theOcelot
There's always goto. A couple goto's to a single "end" label doesn't strike me as too heinous.


That would mean to replace every return with a goto. It's not that bad, really, but everyone who saw it would be left scratching their heads wondering "what the...?" :) You don't need to defend this solution with me, but you would with 9 out of every 10 programmers who work with you.

Quote:
Can't you fake closures with local classes, or something like that?


Right, that's what I'm doing :)

ToohrVyk: Couldn't agree more!

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.