6 ms·
It's pretty arguable to claim that C++ is the only language with RAII. C# has "using", Java has try-with-resources, Python has the "with", etc. - all of which
by derriz 6y ago
It's pretty arguable to claim that C++ is the only language with RAII.
C# has "using", Java has try-with-resources, Python has the "with", etc. - all of which support RAII patterns.
- quietbritishjim 6y agoThose 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.
- 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.
- noctune 6y ago`using` composes badly. It won't work with eg. a list of Disposables or Linq.
- ben509 6y agoThis is the strongest argument against claiming "using" or Python's context managers are RAII. RAII naturally composes because a container has to clean up its memory, and it has a well defined lifetime. Python has a very straightforward model for its context managers, two magic methods: def __enter__(self): ... def __exit__(self, etype, eval, tb): ... To handle many resources being released, the stdlib prefers a more direct approach using a special class: with ExitStack() as stack: files = [stack.enter_context(open(fname)) for fname in filenames] And it makes sense. You probably don't want regular containers to have yet another magic method defined on them, and there is no clear single behavior the a container's __enter__ and __exit__ method could have. And it doesn't compose in other ways. In async code, for instance, there's a completely separate mechanism with `async with` that calls __aenter__ and __aexit__. It really comes down to the fact that when objects have an ambiguous lifetime, which is a consequence of garbage collection, you can't have RAII.
- 72deluxe 6y agoThis is true. But C++'s destructor behaviour means you get this pattern without any thought, and consistently and reliably and deterministically, whereas other types eg. C#'s "using" statement requires explicit usage, which isn't as convenient at all.
- pjmlp 6y agoOnly if the objects aren't heap allocated via new/delete, and you happen to forget to call delete on it.
- secondcoming 6y agoWell yes, C++'s destructor behaviour doesn't work if you don't use C++...
- pjmlp 6y agoWhich is what plenty of people do across the industry with C++ compilers, they code like C.
- secondcoming 6y agoWell that's not C++'s fault. Those people are either stuck on a particular compiler version, are in a maintenance mode, or are just unwilling to improve themselves. Maybe there's job security in being able to write code in a deprecated style.
- pjmlp 6y agoLike Android NDK?
- secondcoming 6y agoNo idea, I've no experience with it.
- gpderetta 6y agoThey are still different: 'using', 'with' are tied to scope, RAII is tied to lifetimes which in turn can be tied to scope but do not have to. It also applies to every object and subobject transitively and implicitly, without having to implement some special protocol [1]. [1] if you are implementing something like a container and dealing to raw memory you might need to call destructors of subobjects explicitly, but because it is needed to correctly reclaim memory, it is almost always done and all standard containers do it already. On the other hand in python list doesn't implement the context manager protocol.
- masklinn 6y agoAll the construct you’re mentioning are nice and useful but they are solely lexical, this means unlike RAII you can’t protect every use of the resource, if you need to return or pass along sais resource they will not work for instance. RAII is strictly more powerful and more regular than context managers.
- deleted 6y ago[deleted]