3 ms·
You're turning RAII upside down... Learn to use proper RAII and you won't need this defer hack.
by tzahola 9y ago
You're turning RAII upside down... Learn to use proper RAII and you won't need this defer hack.
- vortico 9y agoTell that to the APIs I work with, like POSIX sockets/Winsock, etc. You're suggesting that I wrap them all with RAII? That's ridiculous when you have hundreds of C types which are all used just once or twice. Using defer makes elegant/simple C-like code because it uses the C APIs directly and is nearly the same number of lines of code as a C programmer would write without using defer. I fail to see how "learning proper RAII": struct FooWrapper { Foo *foo; FooWrapper(...) { foo = initializeFoo(...); if (!foo) throw FooException(...); } ~FooWrapper() { destroyFoo(foo); } } try { FooWrapper fooWrapper(...); } catch (FooException &e) { ... } is easier than Foo *foo; if (initializeFoo(foo, ...)) { ... } defer({ destroyFoo(foo); }) If this isn't what you meant, could you demonstrate?
- lkjalskdjflkasd 9y agoI can tell by your example you don't know idiomatic C++, keep it mind it takes years to get to that point. Consider using noexcept or just use C.
- yorwba 9y agoYou could use a std::unique_ptr with a custom deleter: http://en.cppreference.com/w/cpp/memory/unique_ptr http://en.cppreference.com/w/cpp/memory/unique_ptr
- vortico 9y agoThis is a reasonable suggestion since the code isn't too bad. std::unique_ptr<Foo, std::function<void(Foo*)>> p(new Foo, [](Foo* p) { if (p) destroyFoo(p); }); // initialize Foo and set to NULL if failed It increases complexity a bit because you no longer have a simple pointer, and you can't allocate on the stack anymore (my example should have declared `Foo foo;` with `destroyFoo(&foo)`, sorry for typo.)
- contravariant 9y agoIf you didn't want a pointer you could have just used something like: template<typename TFoo, typename TDestr> struct FooWrapper { TFoo foo; const TDestr destroyFoo; template<typename TInit> FooWrapper(Tinit initializeFoo, TDestr destroyFoo) : destroyFoo(destroyFoo) { foo = initializeFoo(); if (!foo) throw FooException(...); } ~FooWrapper() { destroyFoo(&foo); } } Although really that should have been part of the "Foo" class itself, but if you need to deal with external code I suppose that might not be possible.
- vortico 9y agoOh yes, I forgot to make it clear that `Foo` is a C struct from a C API. There are tens of thousands of such libraries. An issue with your wrapper is that it is not generic enough and thus must be written for each Foo-like object. That could likely be fixed with more template arguments, but of course there are multiple ways to initialize C objects like - `fooInitialize(Foo foo); // expects that foo is pre-allocated` - `fooInitialize(Foo foo); // allocates and sets the Foo pointer. Annoying, but some libraries do this.` - `Foo fooInitialize(...); // taking certain arguments` A single `defer` wrapper allows you implement custom destruction behavior for each instance, which is useful if you want to set flags or log errors in the destruction process.
- contravariant 9y agoWell you probably have your reasons for needing an absurdly general method of handling things. That said I think it would be a lot cleaner to use locally define a class that takes care of both initialisation and destruction when possible, rather than one that only takes care of the destruction. I wouldn't mind too much if you used macros to simplify the boiler plate somewhat. What worries me is that using 'defer' leaves absolutely no possibility for reusing the code, other than by literally copy pasting it, which just rubs me the wrong way. It goes completely against the DRY principle.
- tzahola 9y agoIt's up to you to litter your code with these `defer` blocks. Although I'm a bit skeptical about POSIX sockets being such an obscure API that it's not worth creating a proper RAII container for them. (not to mention the pre-existing boost socket library) To each their own ¯\_(ツ)_/¯
- vortico 9y agoA general rule in C++ programming is that time spent "perfecting" your code increases, the more difficult it is to change and maintain it, and the probability of using Boost approaches 1. Using Boost as a dependency makes your project very cumbersome for to users to compile by themselves. I put perfecting in quotes, because I believe overengineered, verbose code is something that makes code worse for the reader, writer, and tester. This is why startups are able to accelerate faster than enterprise programmers while they are still able to do this sort of thing. My point is that RAII is not for everyone, so much so that a popular modern language was developed which avoids it (Go) and includes `defer` instead.