4 ms·
The fact that Go's defer runs at function end instead of scope end is, in my opinion having coded and reviewed huge amounts of Go code, a bug. It's almost neve
by knorker 6y ago
The fact that Go's defer runs at function end instead of scope end is, in my opinion having coded and reviewed huge amounts of Go code, a bug.
It's almost never what you want, and it requires terrible gymnastics whenever the difference matters.
So in C++ implementing defer the right way is to just register a std::function that gets run from the class destructor. I've used it as an alternative to wrapping a resource, or timing a function call.
Only gotcha is to be careful about throwing from within the destructor. Or to put another way: don't.
- jerf 6y agoI'd like "defer_func" to be an option; I do have some code that seriously uses it that way. But all in all defer defaulting to scope would be a lot more useful. I've written some functions-in-functions solely to get "scope-like" deferral. Now, so far, none of them have ever survived refactoring, in that they've always been lifted up to become their own top-level functions where defer resumes working like I wanted anyhow. But I've also got a ton of code where the difference doesn't much matter (a function with only one scope, or where defer is only on the outer-most scope anyhow), and it still seems like scope would be a better default.
- knorker 6y agoI've written this a few times: class defer{public:defer(std::function<()> f):f_(std::move(f)){} ~defer(){f_();}private:std::function f_;}; Then fd = socket(…); defer _([]{close(fd);}); But yeah, most of the time a refactor will instead create a proper wrapper class. It's rare that I want to RAII something, but only in some cases. E.g. I probably want RAII on FDs always, with proper ownership, not merely in some cases. And this is also what Go missed. I'm a big fan of RAII.
- gpderetta 6y agoNo need to box with std::function. Since C++17 this is enough: template<class Fn> struct defer { Fn fn; ~defer() { fn(); } }; template<class Fn> defer(Fn) -> defer<Fn>; int main() { defer _ { [] { std::cout << "exit"; }}; } Although you might want to add the ability to dismiss() the guard. edit: a more complete implementation: https://godbolt.org/z/6675K1 https://godbolt.org/z/6675K1 . I like to name my guards. Making them anonymous is left as an exercise for the reader.
- knorker 6y agoYeah, I chose not to templatize and add dismiss() mostly to make it fit better in a comment. But like I said at some point, if it gets complicated, then probably the value should be properly wrapped, with actual owner semantics rather than defers.
- gpderetta 6y agoYou are right in most cases. I find it useful for transactional code though, where I might be modifying a bunch of data structures and I want to undo everything on error. Doesn't really make much sense to write classes in this case if the code is really function specific.
- crazypython 6y agoThe D Language has scope(exit), instead of defer. Example: scope(exit) writeln("3"); writeln("1"); writeln("2"); Output: 1 2 3
- grok22 6y agoThe article references 'defer' being in several newer languages, but seems to have missed the 'scope' implementation in the D language which I think was implemented long before the other languages got their 'defer' feature.
- lerno 6y agoNo, I'm aware of it. GCC also has a similar feature as a C extension. I picked those languages because they very clearly use the same keyword.