Go!
A month ago I started learning Go, since I can’t learn by doing a ton of simple samples I started implementing a physically based renderer (may be more on this later). While reading about the language features there were a couple that sounded really great. The main one being defer calls.
Defer
The idea behind defer is simple: “execute X at the end of this method”. So wherever the method “ends” you can be sure X will be executed. This sounds simple, but if you have a complex method with multiple return statements, things can get tricky.
Lets illustrate this with an example from [0]:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
func CopyFile(dstName, srcName string) (written int64, err error) { | |
src, err := os.Open(srcName) | |
if err != nil { | |
return | |
} | |
defer src.Close() | |
dst, err := os.Create(dstName) | |
if err != nil { | |
return | |
} | |
defer dst.Close() | |
return io.Copy(dst, src) | |
} |
“Awesome” is the word you are looking for.
In Python’s words
Python has something somewhat similar, which is the with statement. You can read about it in many places, for example [1].
Yeah, it’s far from a defer, but you have a really nice way of creating controlled execution in terms of keeping the state as you want it without much trouble. Either you use the Python implemented classes, like file (with open(blah) as f:), or you can create your own object with __enter__ and __exit__ implemented and you can with’ it all you want.
Show me that C++
Actually, these tricks can be done in Python too, but given the handling of scopes, it might get tricky. So I’ll just keep this C++ for now.
So, I read a couple of days ago about a trick like defer with a std::shared_ptr with a lambda deallocator which acts as a defer call when the object gets deleted at the end of its scope. It might sound nice, and under certain circumstances you might even say it’s reasonable… may be. But I don’t like it very much. You know, this may be one of those “tabs vs spaces for indentation” discussions.
Later on, I read about another implementation that did basically the same as the shared_ptr one but in a more correct way, if I may call it that way. Basically you create an object with a lambda as a parameter, and that gets called in the object’s destructor at the end of its scope. We are almost there.
There is at least one issue here, one of the nice things about Go’s defer is that you can do this:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
func b() { | |
for i := 0; i < 4; i++ { | |
defer fmt.Print(i) | |
} | |
} |
and it’ll print 3 2 1 0, because it’ll execute it in LIFO order. But with C++ you cannot do that, because if you declare an object inside a block {}, it’ll get deleted at the end of it, which would be at the end of each loop. So we need some more C++ sugar to try to emulate Go’s defer to the last detail.
Implementation
Why don’t we skip all these words and see some code? Ok.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <iostream> | |
#include <functional> | |
#include <stack> | |
class Deferrer | |
{ | |
public: | |
Deferrer() {} | |
~Deferrer() { callAll(); } | |
void addCall(std::function<void()> &&func) | |
{ | |
_callStack.push(std::forward<decltype(func)>(func)); | |
} | |
private: | |
std::stack<std::function<void()>> _callStack; | |
void callAll() | |
{ | |
while(!_callStack.empty()) | |
{ | |
_callStack.top()(); | |
_callStack.pop(); | |
} | |
} | |
}; | |
// We might want perfect forwarding here, but that's in the TODO list for now | |
#define defer(…) \ | |
do { \ | |
auto deferred = std::bind(__VA_ARGS__); \ | |
__deferrer.addCall(deferred); \ | |
} while(0); | |
#define allow_deferred() \ | |
Deferrer __deferrer; | |
class A | |
{ | |
public: | |
A() {} | |
~A() { std::cout << "Finishing A…" << std::endl; } | |
void f() { std::cout << "Inside A::f" << std::endl; } | |
}; | |
void func(int &val) | |
{ | |
A a; | |
// the allow_deferred call should be after all local variable declarations | |
allow_deferred(); | |
defer(&A::f, &a); | |
val = 1; | |
for(int i = 0; i < 4; i++) | |
{ | |
defer([=]() { std::cout << i << std::endl; }); | |
} | |
defer([&val]() { val = 42; }); | |
} | |
int main() | |
{ | |
int i = 0; | |
func(i); | |
std::cout << "i = " << i << std::endl; | |
return 0; | |
} |
Something worth mentioning regarding this implementation is that you need to run allow_deferred() after all the objects you want to defer a call, otherwise you might get into a problem. A solution would be to use std::shared_ptr, but that depends on the method you are working on.
Other than that, it would be nice to avoid macros but given the approach I’ve taken, it is not possible. A solution would be to have a Deferrer singleton that handles “temporal marks” so you mark it when you run allow_deferred() with the current time and the deferrer will execute the methods until a mark is found. This has two pitfalls, the first is that you’ll need to also call a end_deferrer() at the end of the method, which would defeat the purpose of handling multiple return methods (and obviously forgetting about calling something at the end). And the other problem is that you will need one deferrer per thread (or something equivalent) if you are running in a multithreaded app. So, all in all, I think this approach is a good compromise.
Oh yeah, here’s the output:
3
2
1
0
Inside A::f
Finishing A…
i = 42
Is this a complete solution?
According to [0], there a couple of things that defer does:
A deferred function’s arguments are evaluated when the defer statement is evaluated
This is not true if you bind a parameter, but it is if you use a lambda. So, we have more flexibility but we have to be carefull, as usual.
Deferred function calls are executed in Last In First Out order after the surrounding function
This is true, as we’ve seen from the example. Not much more to say.
Deferred functions may read and assign to the returning function’s named return values
Kind of true, C++ doesn’t have named return values, but anything passed as reference (or a pointer for that matter) can be modifyable if treated properly in the deferred call (lambda with capture by reference, or copy if handling std::shared_ptr).
References
[0] http://blog.golang.org/2010/08/defer-panic-and-recover.html
[1] http://effbot.org/zone/python-with-statement.htm
std::function in this context is not nice – it involves heap allocation.
Here’s ScopeGuard from Andrei Alexandrescu class that does exacly same thing: http://www.drdobbs.com/cpp/generic-change-the-way-you-write-excepti/184403758 (from year 2000! 🙂
Also here’s a more modern ScopeGuard11 for c++11: http://channel9.msdn.com/Shows/Going+Deep/C-and-Beyond-2012-Andrei-Alexandrescu-Systematic-Error-Handling-in-C
I’ve had that talk in my to-see list for a while now, I’ll bump its priority right away.
This is a special case of a ScopeGuard, so it depends on what you want to accomplish.
Why do you say that having the func in heap is a bad thing? I understand that it would be problematic in extreme error situations, but in those cases may be you need some kind of google breakpad behavior, since the env would be really delicate.
Thanks for the links!
When someone writes an piece of writing he/she keeps the idea of a user in his/her brain that
how a user can understand it. Therefore that’s why this piece of writing is amazing. Thanks!