6 ms·
Those are extremely limited in comparison. For example, in C++ you can return a locked mutex-locker from a function: std::unique_lock<std::mutex> foo(); T
by quietbritishjim 6y ago
Those are extremely limited in comparison. For example, in C++ you can return a locked mutex-locker from a function:
std::unique_lock<std::mutex> foo();
The unlocking of the mutex is then tied up with what you do with the returned mutex locker e.g. if you ignore it then it will be unlocked at the end of the current statement, if you assign it to a local variable (and don't move from that) then it will be destroyed when leaving that enclosing function, or you return it from that enclosing function to extend the length of the lock still further.
You also can't compose those mechanisms so easily. For example I could make a class with a std::unique_lock<std::mutex> and a pointer to the data it protects and pass that around.
- chronial 6y agoAt least for python, using is not more limited, but more complete: It allows to handle exceptions that are raised in the block and it also allows you to raise exceptions in the "destructor". That is something that you actually need rather frequently. For example a db transaction wrapper might want to rollback instead of commit if we leave the block via an exception. You also want the "destructor" of that block to raise if the transaction can't be committed. The only thing that the c++ method does really well is memory management (e.g. unique_ptr), because free() can't fail. But that use case is of course not a thing in gc languages. Regarding the composition: Everything that's not memory management usually has side-effects and often wants to do something with regards to exceptions. So it's a good thing for the caller to know that the "RAII" is happening. Allowing you to complete hide that in your class is not a benefit. C++ paints a distorted picture here, because its method is great for memory handling issues, but only OKish for everything else. But most of your RAII issues in C++ are memory related, so you don't notice the problems that much.
- jcelerier 6y ago> At least for python, using is not more limited, but more complete: It allows to handle exceptions that are raised in the block and it also allows you to raise exceptions in the "destructor". the difference is that with Python, the programmer can forget to use "using". In C++, as soon as you instantiate your variable on the stack somewhere, you know that ~T() will happen unless you get a segfault... > For example a db transaction wrapper might want to rollback instead of commit if we leave the block via an exception. I'd say that a transaction wrapper would have an explicit commit method which applies the transaction and "empties" the transactoin object - destructor would always rollback if there is something that was not committed - and if commit was called then there is nothing to rollback anymore so ~T() does nothing.
- geofft 6y agoThe way you'd do this in Python is something like @contextlib.contextmanager def get_a_locked_thing(): with lock: yield thing and then with get_a_locked_thing() as thing: do_stuff_with(thing) Exiting this "with" block causes get_a_locked_thing to resume at its yield statement, which exits that "with" block, which releases the lock. Throwing an exception also exits the context managers. If you were to try omitting the "with" statement and running, say, "thing = get_a_locked_thing()", the function hasn't executed yet (because you haven't entered the context manager), meaning that not only is 'thing' the wrong object, you also haven't even gotten the lock yet. So you won't deadlock/leak the lock, and you will notice in the most basic of tests that your code isn't doing the right thing. I do agree that this is nowhere near as nice as having an object in a local variable, because "with" adds an extra layer of indentation, and that's an advantage of C++/D/Rust-style RAII. But it's definitely doable in Python and pretty idiomatic.
- quietbritishjim 6y agoAh good point, that is perfectly doable. But even accepting the explicit with statements, it's still not as composable in more general situations. For example, I don't see how to reliably pass an active context manager to a function for it to unlock part way through its execution, or return one of two active context managers (if you tried using your technique then both would still be active if you couldn't exit the undesired one before the yield). But maybe you're right that these are a bit less common, except for memory management, than I'm imagining.
- quietbritishjim 6y agoThe one example I gave in the parent comment already addresses everything you talked about. Yes you can use a Python context manager (that's what you're referring to) to lock a mutex, but you can't pass that context manager as a return result of a function and be sure its __exit__ method will be called appropriately in the three situations I mentioned (immediately if result thrown away, at the end of the function if stored in a local variable, later still if returned from that outer function). In fact I don't think you can return a context manager from within a "with" block at all, even accepting that the parent function will have to manually reuse it in another with block, because its __exit__ method will already be called in the inner function. Technically this is true in C++ too of course because the object in the inner function will have its destructor called, but its move constructor will be called first giving you an opportunity to clear out its internal state (this is why my example used a unique_lock rather than a lock_guard). Things are even better in Rust: the bytes of the object are directly copied to the new object's footprint and the original destructor isn't called, so you don't even need to set up a dummy "empty" state. > The only thing that the c++ method does really well is memory management (e.g. unique_ptr), because free() can't fail. Mutex unlocking usually can't fail (and if they do, there's usually not much you can do about it). Same with closing network connections. Closing files can fail, and for some programs it's very important to know when that happens, but for many others it's not important at all. Python's chained exceptions are very nice, but not so important that their absence from C++'s exceptions (or Rust's panics) makes those languages useless.
- gpderetta 6y agoRAII is great for transactions. In a proper robust design, commits should always be explicit and destructors only rollback uncommitted transactions. If your rollback can fail, well, then you have bigger issues. There is now enough introspection in the language to actually implement implicit commit in a non exceptional path, but I think it would be a mistake.
- pjmlp 6y agoSo let me make it boom in C++, auto my_leaky_mutex = new std::unique_lock<std::mutex> {foo()}; Yes, I am making it fail on purpose, but copy-paste in enterprise codebases does wonders for code quality.
- eMSF 6y agoYou're reaching. I'm not sure it adds much to parent's point.
- UncleMeat 6y agoUsing "new" is code smell. This is easily caught in code review.
- pjmlp 6y agoLike those that Google does for Android?
- CyberDildonics 6y agoRaw pointers for ownership are a bad idea, one of google's 30,000 programmers doing it doesn't change that. You are the only one pointing to google as an authority.
- pjmlp 6y agoI am the only one, because the others don't care to point anything.
- CyberDildonics 6y agoOther people are pointing to the actual fundamental reasons these things exist and are valuable, you are pointing to a bizarre false authority as a desperate rationalization for not liking C++.
- UncleMeat 6y ago
- secondcoming 6y ago> For example I could make a class with a std::unique_lock<std::mutex> and a pointer to the data it protects and pass that around. This is pretty much what we do in our code base, except that the class has lock(), tryLock(), etc functions that take a lambda. The functions acquire/try to acquire the mutex and then pass a reference with appropriate const-ness to the lambda.