16 ms·
Yes, agree. I think RAII is one of the greatest inventions in programming ever and basically only C++ has it. Note that it is not just about managing memory but
by cjfd 6y ago
Yes, agree. I think RAII is one of the greatest inventions in programming ever and basically only C++ has it. Note that it is not just about managing memory but any kind of resource like files and so on. I once made an HtmlElement class that would write the opening tag in its constructor and the closing tag in its destructor.
- ncmncm 6y agoD and Rust are the only languages that have since adopted the destructor.
- richardwhiuk 6y agoObjective C also has destructors.
- ncmncm 6y agoNot the same, and overwhelmingly less useful.
- setpatchaddress 6y agodealloc is pretty much the same thing, and it can be used in the same way. It's not "useful" in the C++ RAII sense because: - the standard library design -- every object is refcounted and any code that references the object can add it to an autorelease pool, so you can't safely predict when dealloc will be invoked - Objective C doesn't support stack-based objects
- richardwhiuk 6y agoYou can do stuff: @autoreleasepool { RaiObj* obj = [RaiObj new]; [obj autorelease]; //... stuff } will de-allocate at the end of the block if nothing else hold onto it, but it's obviously less neat.
- bestouff 6y agoI tend to see Rust as "only the good parts of modern C++, and then some".
- aldanor 6y agoAnd also: "Minus all the OOP, and then some."
- saagarjha 6y agoModern C++ generally doesn’t use OOP all that much anyways. However, <algorithm> and <type_traits> in Rust’s standard library would be quite nice to have.
- pjmlp 6y agoOther than the classes used to implement all those STL concepts.
- johannes1234321 6y agoTypes are being used to create those things. But it's not OOP as many people understand it. (Inheritance, everything via method calls etc.)
- pjmlp 6y agoAlongside Object Pascal back in the old MS-DOS and Apple days, Ada 95, Swift, Python, and a couple of others that I won't now bother to search for on my papers archive.
- tonyarkles 6y agoHere’s the crucial difference between Python destructors and C++ destructors: C++ destructors run as soon as the object goes out of scope, deterministically. Python destructors run down the road, during the next GC cycle. As an example of why this matters, I’ve got a class called TimeCapture in a project. If I want to instrument how long a block takes (full function, inside of an if, etc) I can add `TimeCapture tc(“my-metric-name”);` to the block and know that the destructor on that object will be called precisely at the end of that block (capturing the end time of the block) I don’t know the rules for Swift or Ada or OP, but I’m guessing they’ll vary.
- kubav 6y agoTo implement scope guard you can use with statement and implement __enter__, __exit__ methods. It is not real RAII as object is not destroyed on exit but it is still quite useful. http://effbot.org/zone/python-with-statement.htm http://effbot.org/zone/python-with-statement.htm
- achn 6y agoHow do multiple destructors get sequenced? Last in, first out? Thanks
- masklinn 6y ago> Here’s the crucial difference between Python destructors and C++ destructors: C++ destructors run as soon as the object goes out of scope, deterministically. Python destructors run down the road, during the next GC cycle. Not true. They run when the object is deallocated. In CPython if the object is not involved in or referenced from a cycle, that’s as soon as it goes out of scope: the ref count goes to 0 and the object gets deallocated. This is trivial to check: create a structure with a noisy __del__ and create an instance from a function, call the function, del will run. The GC has no reason to run at this point, there’s no allocation requiring any sort of reclamation, but if you don’t trust that you can just `gc.disable()` and observe… the exact same behaviour.
- sacado2 6y agoDon't forget good old Ada.
- ncmncm 6y agoAda does not have destructors or anything analogous.
- Jtsummers 6y agohttps://www.adaic.org/resources/add_content/docs/95style/html/sec_9/9-2-3.html https://www.adaic.org/resources/add_content/docs/95style/htm... "Finalize" on objects of type "Ada.Finalization.Controlled".
- ncmncm 6y agoNope. Key words there are "should call".
- Jtsummers 6y ago> Nope. Key words there are "should call". I mean, yes, those words are in there, but I'm responding to your words: > Ada does not have destructors or anything analogous. Which are factually wrong. Finalize is called automatically (per the link). And they are exactly what you say Ada lacks, analogues of destructors. Your complaint, now, is that Finalize and Initialize don't automatically call the same functions in the parent. That's a separate issue, and doesn't demonstrate that Ada doesn't have this thing (a variant of destructors) which it very clearly has.
- ncmncm 6y agoIt fails to implement the aspect of destructors that makes them an architecturally important feature.
- ChrisMarshallNY 6y agoSwift has it. It’s called `dealloc()`. Since the language is fairly tight, and doesn’t have real stack-unwinding exceptions, there’s not a whole lot of utility in it. Apple seems to think that anything that you put in dealloc should end up in the system (like unsubscribing from notifications). C++ is a dangerous, powerhouse systems language. I think it really needs things like destructors, and I’m glad to see its continuing development.
- saagarjha 6y agodeinit, and if they’re from NotificationCenter you haven’t had to do that for a while and for KVO if you’re using the block syntax (you should!) you basically have an RAII “observation” that you control the lifetime of (often you assign it as a property of the object being observed, so it automatically gets cleaned up when the object goes out of scope).
- ChrisMarshallNY 6y agoYou're right (shows how often I use it, and I write Swift every day). Apple is amping up KVO, in general, which I like.
- threatripper 6y agoPython also has destructors (def __del__(self):). They are called at whatever time and whichever order the GC gets around to it.
- saurik 6y agoThe Python people don't want you to rely on this--which I only learned many years later and it frankly ruined Python for me (are least in the abstract, as "practically" I can ignore this for my projects), as I consider "deterministic finalization" to be a critical language feature--but CPython uses reference counting and thereby has predictable deconstruction.
- ben509 6y agoCPython uses reference counting and gc. It's the worst of both worlds: the expense of refcounting, and faux determinism until some innocuous code change causes you to hang on to a reference.
- klyrs 6y agoRefcounting needs a mechanism for cycle breaking, or it's necessarily leaky. This isn't the worst of both worlds; it's simply a consequence of refcounting. The same issue occurs in c++ if you're sloppy with shared_ptr. If you know what you're doing, you can break cycles with weak references in either language. Where Python has diapers, c++ has footguns aplenty.
- int_19h 6y agoThe problem with Python isn't that it has reference cycles. It's that it has a tracing GC that tries to automagically fix those cycles for you. So in C++, if you get a cycle, the destructors do not run at all - which is pretty noticeable. In Python, they run "sometime later", so programmers are less inclined to fix the cycles (indeed, many aren't even aware that it's a problem). But if you provide a non-deterministic mechanism on top of refcounting to break the cycles, and destructors aren't consistently deterministic already, why even bother with refcounting at all?
- johannes1234321 6y agoAlso PHP.
- ncmncm 6y agoSorry, no.
- johannes1234321 6y ago"Sorry No" as in "PHP is a stupid language, stay away!" Which is a point I won't argue. Or in the "PHP has no destructors"? - in that: PHP's object model is an "interesting" mixture of Java and C++. In regards to C++ it has detemrinsitric destructors, which enable RAII. The only complication is that all objects are reference counted (like shared_ptr in C++) and one can easily lose ownership from where one expects it to be. However if you keep an eye on ownership you can rely on reference count being decreased on function exit (be it regular exit or exception) and the destructor being called, in LIFO order as one expects. This isn't used much in PHP as you don't have to manage resources as often as with other languages/domains (in short lived request world one doesn't care whether a database connection is destroyed on function end or only on request shutdown, memory is handled automatically, etc.)
- derriz 6y agoIt'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.
- 6y ago
- cjfd 6y agoPeople are noticing below that some other languages may have RAII as well. I am quite willing to concede that some languages may have it. After all, it is rather hard to know all languages out there. But it also seems that people are a bit too eager to add languages to the list. If you either have to use 'using' or something like it it really does not count. My question would be whether you can do the HtmlElement example that I mentioned in my post. If you cannot, for instance, because the closing tag would be written whenever the garbage collector feels like it, it really does not count.
- ncmncm 6y agoYes, really only D and Rust, nothing else. If you think other language X supports RAII, you don't really understand it. It's not really compatible with obligate-GC. Finalizers, in particular, are not it. The thing about destructors is that reclaiming memory is only their most trivial use. In C++, as in D and Rust, and Haskell, too, catching errors is the type system's most trivial use. It does the heavy lifting. If your program isn't putting the type system to work, you're toiling away with a screwdriver while people around you are using impact drivers and forklifts.
- int_19h 6y agoIt's perfectly compatible with GC. GC manages memory. RAII manages object lifetime. C++/CLI, for example, has full-fledged C++ RAII implemented on top of GC memory management.
- ncmncm 6y agoFine, but having managed lifetime, it also manages memory, leaving nothing worthwhile for GC to do.
- mhh__ 6y agoD takes the spirit of RAII slightly further too in having scope guards as a first class concept. Not as safe as proper RAII but very useful at the site of declaration to write scope(exit) close(file); or similar
- aldanor 6y agoScope guard in Rust: https://docs.rs/scopeguard https://docs.rs/scopeguard
- fctorial 6y agorust provides destructor functionality with drop trait: https://doc.rust-lang.org/beta/std/ops/trait.Drop.html https://doc.rust-lang.org/beta/std/ops/trait.Drop.html
- giancarlostoro 6y ago> basically only C++ has it RAII is associated most prominently with C++ where it originated, but also D, Ada, Vala, and Rust. https://en.wikipedia.org/wiki/Resource_acquisition_is_initialization https://en.wikipedia.org/wiki/Resource_acquisition_is_initia... Topical sidenote D's got destructors as well: https://dlang.org/spec/class.html#destructors https://dlang.org/spec/class.html#destructors
- guenthert 6y agoThe acronym RAII might be associated with C++, but the concept of releasing resources automatically if a scope is left predates C++. It is present in e.g. Common Lisp in the form of 'with-' macros. See https://wiki.c2.com/?ResourceAcquisitionIsInitialization https://wiki.c2.com/?ResourceAcquisitionIsInitialization
- Foober223 6y agoThe "block scope spirit" is there but not quite the same. A lisp "with-" macro requires you to remember to use it. It's not much different manually writing code to release something, in the fact you must remember and explicitly do it. With C++ RAII you can have a brain fart and forget about freeing a resource, but you're still OK.
- Arnavion 6y agoRAII also follows value scope instead of lexical scope. So if you have a function that creates a value and returns it, the cleanup-via-destructor runs in the scope of the callee, as it should. This doesn't work with lexical-scope-cleanup things like golang's defer (and bugs me when new languages add it instead of destructors). Instead you either have to document that the caller has to remember to defer-cleanup the result, or you have to do funky things like take a callback that receives the value, invoke the callback and defer-cleanup the value.
- int_19h 6y agoIf you have a C++ function that returns by value, the destructor runs twice - first inside the function, to destruct the original, and then inside the caller, to destruct the returned copy. (N)RVO can elide the copy, but it's not guaranteed, and in any case, not always applicable. So destructors are still lexically scoped. What makes C++ flexible in practice is move semantics, which lets you move-return values, such that only the destructor call on the caller side is the one that matters.
- Arnavion 6y ago
- kabdib 6y agoPerforming an operation that can fail in a constructor or destructor will you into trouble. It's a trap. You can check for initialization failure ("remember to call isObjectOkay()" or something like that) but you're basically hosed if a destructor fails. No, exceptions are not the answer. NONE of the C++ projects I've worked on in the last 30+ years have used those in production. Exceptions are far, far worse trap. It's funny that the teams across half a dozen companies I've been in have independently come up with just about the same set of guidelines about C++ features. It's almost like some of those features were bad ideas. :-)
- deleted 6y ago[deleted]
- int_19h 6y agoPerforming an operation that can fail in a constructor is not a problem, because then the initializer throws, and you don't get an object at all - so there's nothing for you to check if it's in a valid state or not. On the other hand, this is exactly the problem that arises if you refuse to use exceptions, because then the constructor has no way to signal that it failed. Since constructors, by their very nature (acquiring resources, validating initializers etc), can fail, you have to resort to "is it valid?" flags, and your API clients have to check those flags or risk unspecified behavior.
- kabdib 6y agoUsing C++ exceptions is just not acceptable in large, production systems. I've worked on several C++ systems with tens of millions of lines of code. Different teams independently reached the conclusion that exceptions were destabilizing, and hid bugs and failures. Take a look at the Google C++ guidelines, as an example. https://google.github.io/styleguide/cppguide.html#Exceptions https://google.github.io/styleguide/cppguide.html#Exceptions They're fine in other languages (python, etc.). Note that golang does not really have them, except in a very crude sense.
- deleted 6y ago[deleted]