Talk:Resource acquisition is initialization
This is the talk page for discussing improvements to the Resource acquisition is initialization article. This is not a forum for general discussion of the article's subject. |
Article policies
|
Find sources: Google (books · news · scholar · free images · WP refs) · FENS · JSTOR · TWL |
![]() | This article is rated C-class on Wikipedia's content assessment scale. It is of interest to the following WikiProjects: | ||||||||||||||||||||||||||
|
![]() | This article links to one or more target anchors that no longer exist.
Please help fix the broken anchors. You can remove this template after fixing the problems. | Reporting errors |
Earlier C++ reference
[edit]The C++ Programming Language (Bjarne Stroustrup) (2nd edition) ISBN 0-201-53992-6 : Ch9 "Exception handling" 9.4.1 "Constructors and Destructors" "This technique for managing resources is usually referred to as 'resource acquisition is initialization'. "
This hints at an earlier usage. 51.9.104.223 (talk) 11:44, 20 May 2021 (UTC)
RAII in Ruby/Smalltalk/Python
[edit]The Ruby/Python examples aren't RAII. The only difference between the python/ruby versions and the Java try/finally approach is that the cleanup method is not shown. Correct usage of a file object (or whatever other resource) still requires that you always use the object within a 'with' block (or whatever the special block is called).
216.39.239.100 21:10, 17 July 2007 (UTC)
And the correct (or rather, RAII enabled) usage of a file object in C++ requires that you always use a scoped object instead of allocating it with new. Stroustrup says: "The key idea behind the ‘‘resource acquisition is initialization’’ technique/pattern (sometimes abbreviated to RAII) is that ownership of a resource is given to a scoped object." This is precicely what Python and Ruby's blocks do. RAII can be used in Java as well by using a callback object, but since defining an anonymous class is so complex (and they have some limitations), everyone just uses try/finally instead. 80.221.27.192 11:25, 1 December 2007 (UTC)
In C++, in any case the lifetime of the variable defines the lifetime of the resource. This is because the variable is the object, and the object's destructor is called when the variable goes out of scope. Using "new" does not change this, but you now have a variable of a pointer type, so the pointer's RAII is not buying you anything unless you use a magical smart pointer of some sort. The object is not tied to the pointer in any way, so the pointer's lifetime does not influence the object and the pointer's destruction does not cause the object to be destructed.
The same thing happens in Ruby. If you just said foo = File.open('foo.txt'), the variable is scoped, but if it goes out of scope, the object it refers to lives on. In the File.open('foo.txt') do |foo| end example, the block is merely a callback and the open method explicitly has to take care of the resource cleanup. The scoped variable's lifetime actually ends before that, but that is not what causes the cleanup, so no RAII is taking place.
129.13.186.1 21:32, 2 December 2007 (UTC)
A line like foo = File.open('foo.txt') may give you a nice scoped autodestructing variable, but it doesn't tell you *anything* about what was done to make sure that the object foo refers to was carefully initialized. Basically this hides all the joy of RAII in a library function, and then asks the reader to imagine that true RAII must have happened inside of that black box. The example where that File.open() is wrapped in the creation of a larger object, on the other hand, *is* more demonstrative of RAII, I just wish the examples actually *on* on the article page were as relevant (though showing the class in use would be good). --C. Alex. North-Keys (talk) 09:46, 6 October 2015 (UTC)
- Not really true. Regardless of implementation details (e.g., a destructor wasn't automatically called that released the resource when the object went out of scope, the block manually released the resource, just before it went out of scope, or the caller released it just afterward), the usage fits the RAII pattern. I suppose something similar can be said for python's with clause, or Haskell's where clause, &c. It's still RAII, just implemented differently (because they are different languages).
- Let's take a C++ example from wikibooks:C++_Programming/RAII:
#include <cstdio> // exceptions class file_error { } ; class open_error : public file_error { } ; class close_error : public file_error { } ; class write_error : public file_error { } ; class file { public: file( const char* filename ) : m_file_handle(std::fopen(filename, "w+")) { if( m_file_handle == NULL ) { throw open_error() ; } } ~file() { std::fclose(m_file_handle) ; } void write( const char* str ) { if( std::fputs(str, m_file_handle) == EOF ) { throw write_error() ; } } void write( const char* buffer, std::size_t num_chars ) { if( num_chars != 0 && std::fwrite(buffer, num_chars, 1, m_file_handle) == 0 ) { throw write_error() ; } } private: std::FILE* m_file_handle ; // copy and assignment not implemented; prevent their use by // declaring private. file( const file & ) ; file & operator=( const file & ) ; } ;
- Now in ruby (using generic exception handling):
class MyFile def initialize(filename, mode, &block) begin handle = File.open(filename, mode) if block_given? block.call(handle) else puts "No block given for RAII" end rescue IOError => ex puts "Got a #{ex.exception} error!" raise #re-raise the exception up the call stack ensure handle.close unless handle.closed? end end end # ... irb(main):018:0> MyFile.new("foo.bar.baz.txt","r") {|f| f.write "foo" } Got a not opened for writing error! IOError: not opened for writing from (irb):18:in `write' from (irb):18 from (irb):6:in `call' from (irb):6:in `initialize' from (irb):18:in `new' from (irb):18 from :0
- All read / write operations are atomic to the block, and the resource is released when the block scope closes (i.e., when control is returned to the calling context). So, in essence, the resource allocated in the constructor, and passed to the block object (yes, it is an object of type Proc), is released when the block object goes out of scope. Rather than an explicit destructor, the constructor acquires the resource, passes control of the resource to the block, and destroys the resource when control is returned. That's the main idea behind RAII, as I understand it.
- Ps. In ruby, you could implement an explicit destructor with Object#finalize, but it is not guaranteed to run--best practice dictates using a block form for RAII. You can also implement a destructor in python with the __del__ hook. 64.234.67.2 (talk) 05:50, 29 December 2008 (UTC)
This is ridiculous nonsense. RAII is a specific way to allocate and release resources ... some totally other way to do it (such as using "ensure") is not RAII. " Rather than an explicit destructor" -- if there is no explicit destructor then it's not RAII. Period. -- 71.102.128.42 (talk) 02:27, 17 November 2014 (UTC)
RAII in Java
[edit]Any tips for RAII in Java? Wouter Lievens 12:39, 11 Mar 2005 (UTC)
- The purpose of a Wikipedia discussion page is to improve the related article, not to provide programming tips. That said, the comment below is correct. -- 71.102.128.42 (talk) 02:32, 17 November 2014 (UTC)
RAII is not applicable to Java because Java lacks determinstic destruction. In C++ a major concern and use of RAII is memory management. In Java, garbage collection handles memory but there are other types of resources that would benefit from the RAII pattern. Unfortunately the closest thing you can get in Java is the dispose pattern.
- I know Java doesn't allow it the way C++ does, that's why I was asking the question :-) Wouter Lievens 19:42, 20 September 2005 (UTC)
This is probably about as close as you can come to the idiom:
public interface Factory<T,E extends Throwable> {
public T create() throws E;
}
import java.io.*;
public abstract class IoRAIIBlock<T extends Closeable> {
private final Factory<T,IOException> factory;
public IoRAIIBlock(Factory<T,IOException> factory) {
this.factory = factory;
}
public final void go() throws IOException {
T obj = factory.create();
try {
run(obj);
} finally {
obj.close();
}
}
public abstract void run(T arg1) throws IOException;
}
public abstract class FileWriterRAIIBlock extends IoRAIIBlock<FileWriter> {
public FileWriterRAIIBlock(final String filename) throws IOException {
super(new Factory<FileWriter,IOException>() {
public FileWriter create() throws IOException {
return new FileWriter(filename);
}
});
}
// more constructors if you want...
}
public class Example {
public static void main(String[] args) throws IOException {
new FileWriterRAIIBlock("logfile.txt") {
public void run(FileWriter logfile) throws IOException {
logfile.append("hello logfile!");
}
}.go();
}
}
I guess if you were feeling really evil, though, you might be able to use AspectJ to insert implicit resource disposal. --pfunk42 (talk) 06:30, 17 May 2008 (UTC)
Since version 1.7 (2011), Java has supported the 'try-with-resources' statement, which I often use for RAII. It is not memory-based release, but the guarantees of calling the 'close()' method upon exiting the block provide the same abilities as stack-unwinding destruction. Pedorro (talk) 19:22, 23 February 2022 (UTC)
- try-with-resource (and more generally the concept of bracketing / context management) is the common RAII alternative in GC'd language (the other big one being scope / defer-type statements which has similar issues), but strictly inferior because it is unable to handle conditional release without additional structure or a dedicated variant, whereas RAII handles this situation naturally. 178.51.135.179 (talk) 07:56, 5 September 2023 (UTC)
Remove "stub"?
[edit]What about removing the stub template? I found this article most helpfull.
- Yes, I think it deserves that removal (I'll do it right now) – Adrian | Talk 23:36, 17 November 2005 (UTC)
RIIA?
[edit]Who uses RIIA? Googling for "Resource Initialization Is Acquisition" only returns 75 results, most are from mailing lists. DanielKO 00:20, 16 March 2006 (UTC)
- Agreed, I've never heard it used in that order either. The instances that you can find on Google [1] are Wikipedia "mirrors" and most likely mistakes otherwise. I'll remove the acronym from the page as per WP:BB for now. -- intgr 11:21, 2 July 2006 (UTC)
- That was my typo. It's been fixed. Sory about that. —Ben FrantzDale 02:25, 3 July 2006 (UTC)
Bad example code
[edit]Part of the point of RAII is that the code be exception-safe. The example is not necessarily exception safe because it can throw from a destructor (CloseFile can fail). Throwing from a destructor is poor coding practice in C++ (see Exceptional C++ by Herb Sutter).
- If CloseFile can throw an exception (or even fail at all), there are more serious problems to handle. :) Usually those functions wrapped on RAII classes aren't even exception-aware (like a C API), so you don't need a try-catch for them. I think that adding more lines for a try-catch in this simple example (thus making it even less clear) doesn't add as much value as adding a foot note about the dangers of exceptions escaping from destructors. Feel free to add this note in the article. --DanielKO 00:48, 4 June 2006 (UTC)
- CloseFile can fail. In most cases WriteFill will not actually write data, just append to the buffer, and CloseFile will flush the buffer, and that can fail (not enough disk space, etc). It's very important that write a catch in the destructor when the resource release can throw exception:
- One of the annoyances of writing in languages with exceptions is now you have to add additional requirements like "Part of the point of RAII is that the code be exception safe". This isn't an issue in languages without exceptions, and dilutes the topic. --C. Alex. North-Keys (talk)
class RAII { public: RAII () { res.Acquire (); } void ExplicitRelease() { res.Release (); } ~RAII() { try { res.Release (); } catch (...) {} } };
—The preceding unsigned comment was added by 83.216.62.233 (talk) 10:49, 11 February 2007 (UTC).
I'm with 83.216.62.233. This requires some change to the article. When reading a file, it's probably ok to do it like this:
{ File f("foo.txt"); cout << f.readLine(); }
..since I can't think of a case when the destructor could throw after a successful read. Many resources can be released without the possibility of failure, so this is how simple RAII is usually. But the example writes to a file, so it should be written like this:
{ File f("out.txt"); f.write("hello"); f.close(); }
In this case, Python and Ruby blocks are in fact simpler because they handle release failure correctly and do not require an explicit .close() call.
So, what should be done to the article? This issue needs to be covered better, that much is certain. It may not apply to D (maybe D's destructors can throw without causing abort when unwinding -- I don't know). Should the example use a scoped_ptr, or read from a file, to demonstrate the simple RAII case that requires no explicit close()? I'll probably change the article at some point.. (edit:) A further "issue" with files is that after closing them explicitely, all the operations must throw an exception. This requires some code bloat (I'd rather see an even shorter example than the current one), and the resource is no longer bound to the object's lifetime, which is supposed to be essential to RAII. 80.221.27.192 11:47, 1 December 2007 (UTC)
This article as a whole has been pretty fluffy for a long time, with C++ shoed in (for example) despite hiding most of the useful aspects that relate to resource acquisition == initialization, and instead highlighting irrelevant sidetracks like mutexes, exceptions, and so forth with examples that utterly lack an explicit A==I part. Unless an example *explicitly* shows something like the code that only returns a created object IFF it's initialized (and not by something buried in a library call used in the example, but actual code), then it has no reason to be in this article. That includes code that only talks about how to get rid of objects -- this isn't an article on resource Deletion, but Acquisition. It's been about 3 years since RAII was demonstrated in the examples, with the article as a whole becoming steadily less useful, largely due to massive deletions by Forderud in 2012 who seemed to think that demonstrating approaches to implementing RAII was outside of the scope of an article *about* RAII. --C. Alex. North-Keys (talk) 09:42, 6 October 2015 (UTC)
Bad C example
[edit]The C example is looks much more complicated than necessary. What about this, that is much better suited for maintenance:
int c_example() {
int retval = -1; // assume error
FILE *f = NULL;
do {
f = fopen("logfile.txt", "w+");
if (f == NULL) {
retval = -1;
break;
}
if (fputs("hello logfile!", f) == EOF) {
retval = -2;
break;
}
// continue using the file resource
retval = 0; // return value 0 is success
} while (0);
// Releasing resources (in reverse order)
if (f != NULL && EOF == fclose(f)) {
retval = -3;
}
return retval;
}
--87.163.200.60 (talk) 11:38, 25 September 2008 (UTC)
The c_example() above, like the one that used to be in the article, only demonstrates the difficulty of dealing with exceptions, not RAII itself. Although talking about both together makes a lot of sense, the topic of exceptions in isolation would be seen in a Wiki article specific to exception handling.
The example on the page now shows where ad-hoc mechanisms in C to emulate RAII tend to lead, with non-local exception handling implemented in conjunction with RAII, and the resulting problems that underscore how having RAII as a language builtin are beneficial. C. Alex. North-Keys (talk) 06:35, 18 October 2010 (UTC)
Why is this code "much better suited for maintenance"? I think it's just longer and using a loop to simulate gotos is bad form. Also the goto idiom is popular; see for example Linux source code. Matonen (talk) 13:57, 15 April 2009 (UTC)
- Often, gotos are seen as evil, since they essentially turn your code into swiss cheese. do { } while (0) also isn't a loop, it's specified explicitly that it is a "breakable block" i.e. you can "break;" out of it in an exceptional case (and even return something from it if you care to use C side-effects). It's commonly macro-ized as STMT(_START/_END), and used from within macros which define blocks of code, because C compilers treat these blocks as a single "statement", hence the name of the macro. Both idioms (goto, stmt) are very popular and equally portable though, so it's really up to the preference of the programmer. The Linux Kernel uses gotos as Linus uses gotos and essentially dictated their use through his BDFL status. —Preceding unsigned comment added by 76.177.52.225 (talk) 02:14, 29 April 2009 (UTC)
The second C isn't particularly useful: there are plenty of things RAII isn't, but this article should focus on things it is. It also reads like an advocacy, rather than an explanation. I intend to replace it with an example of a goto-based RAII, because it is an exceedingly common idiom, as evidenced by its usage in the Linux kernel. I will also include a small blurb about the obvious tradeoffs between the current example and the goto one. 71.188.88.67 (talk) 06:24, 31 May 2012 (UTC)
RAII != Abstraction
[edit]Now the article seems to suggest that managing the resource FILE_HANDLE with a class is RAII. No, that's just basic abstraction and clearly quite possible in Java as well. Without RAII you'd have almost the same LogFile class but you'd have to manually call "close". The code would be more like this:
bool functionB() { LogFile log("Output log of functionB"); //if this throws, no closing is needed since nothing was opened try { log.write("fun Step5 succeeded"); // if the function needs to check for an error and abort accordingly: if( ... ) throw FunctionAFailure(); // EXTRA CODE: the resource needs to be closed explicitly log.close(); // explicitly release the resource return true; } catch (...) { // An exception was thrown during the execution of the function // Time to cleanup the resources log.close(); throw; } }
I think this is more accurate than the current article's functionB.
Your example here doesn't make sense to me. A LogFile class with close() would be called a poor design which fails to use constructors and destructors correctly. Namely, the class invariant is violated once you call close(). It would be a textbook case of misuse/misunderstanding of C++, that is, a failure to understand RAII. Xerxesnine 07:24, 23 February 2007 (UTC)
What class invariant would that be? Did I tell you what the class invariants are? No, you arbitrarily decided that the class should have invariant X and saw that the class doesn't conform to X. How silly. On top of that fallacy, you completely misunderstood what I wrote. I did not suggest this as a good way to write C++, but as a change to the currently presented example to better highlight the differences between RAII and non-RAII approaches. Do you think the current non-RAII example code shows a good understanding of RAII? Of course it doesn't because that's the whole point of the example. That's the whole point of my code as well, since it's meant to replace the one presented in the article. Currently the article shows the difference between non-abstracted non-RAII code and abstracted RAII code. When we want to highlight the benefits that RAII gives you, the examples should obviously be of abstracted non-RAII (my code above) and abstracted RAII -- With only the RAII part being different. —Preceding unsigned comment added by 80.221.24.224 (talk • contribs)
- First of all, thanks for catching the fopen() mistake; of course you're right that it doesn't matter whether fopen() throws or not.
- By class invariant I simply meant the most basic invariant of every class: "this is a valid object". Once you close the file, you have an invalid object: all subsequent operations on the object will fail. That situation is entirely avoidable.
- Since I began my previous comment with, "Your example here doesn't make sense to me," it shouldn't surprise you that I "completely misunderstood" what you originally wrote. Thank you for clarifying. However I would suggest that you could be a more effective communicator if you polished up your etiquette.
- As I understand it, you are saying that the non-RAII example should use a C++ class, because "RAII != abstraction". If we go back to the original OpenFile() and CloseFile() during the time of your original comment in this thread, the C code is as abstracted as the non-RAII C++ code you propose. You don't get more abstraction by using a non-RAII C++ class --- it's the same code but with a different syntax. Instead of CloseFile(file), it's file.close().
- Since the current example illustrates the common and useful practice of wrapping a C API with C++, since we gain nothing (no abstraction) from a non-RAII class, and since we may possibly introduce confusion by showing a "bad example", it is my suggestion that we keep the C code. Xerxesnine 21:48, 12 May 2007 (UTC)
- Ok, sorry for being so hotheaded in my previous message. But I still disagree with you saying that we gain no abstraction from a non-RAII class. The article's file class has a write method which hides 2-4 lines of low-level code. Similarly the constructor hides both opening the file and throwing in case of an error. No sane programmer would write this code every time she tried to open a file:
std::FILE* file_handle = std::fopen("logfile.txt", "w+") ; if( file_handle == NULL ) { throw open_error() ; }
- At the very least, she'd write a function to either open or throw.
std::FILE* file_handle = open_or_throw("logfile.txt", "w+") ;
- I maintain my stand that failing to use these abstractions in the non-RAII code is a dishonest presentation of the benefits of RAII. And the current article puts even more code in the class when compared to a year ago so all the more reason for change. 80.221.24.224 07:29, 22 June 2007 (UTC)
Is it RAII if it's not automatic?
[edit]I feel parts of this article contradicts itself.
To me, RAII means resource management through statically predictable destruction — in C++ by auto variables or std::auto_ptr, or by the lifetime of some larger object having the resource-owning one as a member.
The part that speaks about std::auto_ptr seems to agree with that definition. Another part that speaks of "languages that do not support RAII, such as Java" seems to imply that try ... finally isn't RAII. A third part states that manually calling a finalize() method in languages like Java is RAII.
I suppose you can say you use RAII if you consider your resource FOO as owned by object FOO in general ... but isn't that a more general idea, a basic tool of any object-oriented programming? JöG 20:35, 3 September 2006 (UTC)
Agreed. The final word in the acronym, initialization, refers to the construction of an object where resources are acquired. If you're not doing that, then it's not RAII. Java does not support RAII, but instead has the try-finally facility to guide resource management. I just changed the "Limitations" section to remove this confusion. Xerxesnine 12:49, 23 February 2007 (UTC)
Perl
[edit]The RAII pattern is used ubiquitously in object-oriented Perl code. In fact, Perl programmers don't even call it RAII, or anything else, since it is simply normal behaviour. Perl's dynamic nature makes it more flexible than C++, so you can safely write:
sub open_it { return new IO::File("it.dat"); }
in Perl, and the file will be automatically closed when you were done with it, whereas in C++ if you did
std::ofstream open_it() { return std::ofstream("it.dat"); }
it would fail to compile, and if you did
std::ofstream *open_it() { return new std::ofstream("it.dat"); }
it would compile but you'd need to explicitly delete the pointer to close the file.
Unfortunately, I can't find a way to cram this vital information into the article. Perhaps someone could add a section for languages that use RAII as standard practice (assuming there are any others)?
--61.214.155.14 05:31, 22 November 2006 (UTC)
- This is not RAII. The file object will be closed and destroyed by the garbage collector, not explicitly when the object goes out of scope of the function. -- intgr 07:45, 22 November 2006 (UTC)
- Exactly. The RAII C++ version looks like this:
std::auto_ptr<std::ofstream> open_it() { return std::auto_ptr<std::ofstream>(new std::ofstream("it.dat")); }
- Of course, if this article gave one person the wrong impression, could probably be improved. —Ben FrantzDale 01:41, 12 February 2007 (UTC)
- You speak authoritatively while having no idea what you're talking about; Perl doesn't have a garbage collector, it has reference counts. The file is closed -- deterministically -- when the last reference to the IO::File is dropped. -- 98.108.208.207 (talk) 13:15, 15 July 2008 (UTC)
- I stand corrected. It had been a while since I'd progreammed in Perl; now it's been three years longer. :-) —Ben FrantzDale (talk) 11:49, 26 March 2010 (UTC)
- Reference counting is a form of garbage collection. The whole point of RAII is that the object's destructor is called without memory management being involved. -- intgr [talk] 17:35, 26 March 2010 (UTC)
- "Reference counting is a form of garbage collection." -- No it isn't, any more than C++ calling destructors when objects go out of scope is garbage collection, nor is shared_ptr garbage collection. " The whole point of RAII is that the object's destructor is called without memory management being involved." -- No, that isn't the whole point. The whole point is exception-safe deterministic resource management. Calling destructors when reference counts go to zero has the same point, and is represented in C++ with shared_ptr. -- 71.102.128.42 (talk) 02:46, 17 November 2014 (UTC)
stlsoft::scoped_handle
[edit]My first reaction is that this new section is ugly and superfluous. A simple class wrapper for OpenSocket() and CloseSocket() is just the right thing to do. In contrast, the scoped_handle<> template obscures the programmer's intention.
My inclination is to remove this section entirely, as it contributes nothing to the concept of RAII and appears to be an exercise in unwarranted cleverness. Xerxesnine 22:27, 31 March 2007 (UTC)
- Agreed, one example is enough and the new one is rather nonobvious, providing little encyclopedic value. -- intgr 22:36, 31 March 2007 (UTC)
Failure inside the file destructor
[edit]Release failure is a complex situation with RAII. The various approaches have been discussed at length on comp.lang.c++ and comp.lang.c++.moderated, all of which are probably inappropriate for an introduction to RAII. That's why I made a simple assert in the destructor; it seems better to do something than to simply leave it unchecked as Intgr wished. Think of it as a placeholder for user-defined functionality. Xerxesnine 14:19, 15 April 2007 (UTC)
- No code is better than misleading code. This:
- if(std::fclose(m_file_handle) != 0) assert(!"file failed to close");
- is definitely the worst example of using assertions that I've ever seen; assertions are for verifying certain conditions and catching developer errors during the debugging phase, and NEVER for catching/handling runtime errors, or simply emitting error messages.
- Runtime errors are errors that may occur even when the code does not contain any bugs, e.g., hardware errors, user errors, whatnot. An I/O error is such a runtime error.
- Typically, assertions are not even compiled into release builds of C/C++ programs, so the body of this if() condition would expand into a no-op and your "error check" is not actually checking anything in cases where it matters.
- -- intgr 19:43, 15 April 2007 (UTC)
- We all know what assertions are, so most of your comment is unnecessary. There is an error condition for fclose(). Not checking that error is more misleading than anything you could possibly imagine. We must check the error. Given the complexity of RAII release errors, I went with a simple assertion flag; as I said, it's a placeholder. Remember this is supposed to be a simple example.
- Now, how do you propose that we check the error? If you don't like the assertion, then please suggest an alternative. Leaving it unchecked is not acceptable. Xerxesnine 20:08, 15 April 2007 (UTC)
- Adding an assert there does not constitute "checking for the error". Repeating myself: assertions should never be used for checking runtime errors, since they are left out of release builds, where runtime errors matter the most. Assertions catch developer errors (bugs), not runtime errors.
- Thus, the example was more misleading than useful — leaving the impression that the error was checked, while it really wasn't. In addition to that, it was a misuse of assertions: the example also included an awkward "!" hack to evaluate a string to false (WTF?!), and assert()ed to a constant inside an if() condition, instead of actually asserting a condition.
- I do not have a good alternative, as I rarely write C++, and avoid overuse of patterns such as RAII. I do, however, use assertions. Even a cout << "error closing file\n"; exit(1); would be better in this case.
- -- intgr 22:29, 15 April 2007 (UTC)
- The assert(!"something bad") idiom is common. You'll find it in books by Herb Sutter and Andrei Alexandrescu (at least one of those). It's a good technique.
- Since you rarely write C++, I advise you to look through all of this thread and spend some quality time googling on RAII before writing anything else on this page. Assertions are in fact a common method of dealing with release failure, even preferred by some. After all, when A B C is done, this forces the undo action to be C^(-1) B^(-1) A^(-1). If the latter fails, we must assume in general the state is undefined --- which is an assertion (yes, even in production code).
- As I hoped to convey in my last comment, your preaching on assert() completely misses the point. You also missed the point that the assert is essentially "placeholder" for user-defined code. It is better to notify the reader that this condition exists than to keep silent (remember the audience). I'm willing to bet 100% of experienced programmers would agree.
- Your suggestion to use std::cout for error reporting (actually it should be std::cerr) is similar to what assert(!"file failed to close") does, only the latter does it better. You seem to be fixated on the assumption that all assertions are removed from release builds, which is most certainly not the case. Indeed, in practice many are retained --- especially those such as file release checks which are unlikely to ever become a bottleneck. Xerxesnine 05:22, 16 April 2007 (UTC)
- "Assertions are in fact a common method of dealing with release failure, even preferred by some"
- So a cryptic error message stating "whatever.cpp:553: file::~file(): Assertion `!"file failed to close"' failed." is somehow considered more meaningful to the user than a pop-up stating "error writing file, your changes may be lost"? That is certainly one of the anti-patterns of using assert — assertion failures are only useful to the developer.
- "the assumption that all assertions are removed from release builds"
- Yes, because this is the default behavior — assertions using the standard assert() macro are stripped when the NDEBUG macro is defined, and most IDEs define that by default for release builds. Look it up in your favourite C++ language reference.
- "that the assert is essentially "placeholder" for user-defined code"
- Then why not just make that clear with a comment?
- "especially those such as file release checks which are unlikely to ever become a bottleneck"
- You cannot conditionally choose which asserts are included and which are not, unless you are using nonstandard assert macros. Assertions are also useful in inner loops, especially when thrown out of release builds.
- "better to notify the reader that this condition exists than to keep silent"
- Certainly, but misleading (tricking) the reader is not the right way to do it.
- "spend some quality time googling on RAII before writing anything else on this page"
- I am not planning to edit anything RAII-related on this article, so I'll pass. I am only complaining about your use of assertions.
- -- intgr 10:28, 16 April 2007 (UTC)
So a cryptic error message stating "whatever.cpp:553: file::~file(): Assertion `!"file failed to close"' failed." is somehow considered more meaningful to the user than a pop-up stating "error writing file, your changes may be lost"?
Please read the comp.lang.c++.moderated link I gave before continuing down this line of thought. That's why I gave it to you. You are also missing the context of this simple example; we obviously can't include pop-up dialogue box code.
Assertions are useful for terminating when we detect an undefined state, at which time continuing execution may do real damage. The loss of a file handle is one such case, which may indicate a hardware failure.
You cannot conditionally choose which asserts are included and which are not, unless you are using nonstandard assert macros. Assertions are also useful in inner loops, especially when thrown out of release builds.
Yes, you can make conditional use of asserts within the same build, with the standard NDEBUG macro if desired. That's the whole point of using assertions. That you don't understand this is another indication of the fact that you rarely write C++. Your confusion over assert(!"bad state") is yet another indication, among others.
You continue to provide needless informational comments about how assert() works. Your first post on this thread consisted of entirely of such comments. Be assured that I understand. In light of your inexperience with C++, you might be more productive if you reconsidered your role as "educator" here, engaging with others as peers instead.
The overall issue is a practical one. How are we to handle the release error? There is no good answer because the usual solutions are complex and therefore inappropriate for this introductory example. My suggestion was a simple assert. Your first suggestion was to ignore the error. That is universally unacceptable. Your second suggestion was a std::cerr notice, but as I said that is almost the same thing but worse: it lacks the file, line number, and hardware breakpoint which assert() normally entails.
So, what do you suggest as an alternative to the assert()?
Xerxesnine 16:04, 16 April 2007 (UTC)
- "Yes, you can make conditional use of asserts within the same build, with the standard NDEBUG macro if desired."
- Technically, yes; however, redefining and re-including the assert header is exactly the kind of misleading behavior that should not be relied on in real code, let alone examples, given that it is the only header defined by C99/C++98 that changes its behaviour with multiple inclusions.
- Nor does this address my concern at all, since you did not override NDEBUG in your example.
- "Your confusion over assert(!"bad state") is yet another indication, among others."
- Where did you get the impression that I was confused? I said that it was "an awkward "!" hack to evaluate a string to false". Are you saying that my analysis is incorrect on the purpose? I already made a point on how assertions should be used.
- Anyway, back to relevant discussion.
- "Assertions are useful for terminating when we detect an undefined state"
- Correct. However, I/O errors are not an "undefined state"; as noted twice above, they are legitimate runtime errors. I would expect a program to handle them and inform the user, instead of crashing (which an assertion failure or "undefined state" effectively is).
- The loss of a file handle is by far not the only error that fclose() can return: fclose() is also responsible for flushing the latest changes to the file, which may fail, for example, when the user has pulled out their USB stick too early, or when the HD is full — conditions that are bound to happen in practice, but rarely do during development.
- While crashing means that the user has lost their work, a gracefully handled error would presumably allow the user to save their work on a different medium. I assumed that you would understand this, seeing you introducing a check for that error in the first place. Hence I started explaining why an assertion was inappropriate for handling this case. Given that you do understand assertions, I have to conclude now that you are simply negligent of handling such runtime errors.
- "Your first suggestion was to ignore the error."
- That was not my suggestion at all — I suggested not to include a piece of code that I considered misleading and a cookbook example of an assert anti-pattern. My previous post did propose a real suggestion — if the line was supposed to be a "placeholder", then a comment explaining what should be placed there would certainly be warranted, preferably replacing the assertion.
- "you might be more productive if you reconsidered your role as "educator" here, engaging with others as peers instead."
- I explained above why I considered it necessary to explain the purpose of assertions, and I contend that my argument for runtime errors still stands. I did not consider myself an "educator" to begin with; I apologise if I came off to you that way.
- -- intgr 17:48, 16 April 2007 (UTC)
- Yes, you can make conditional use of asserts within the same build, with the standard NDEBUG macro if desired.
- Technically, yes; however, redefining and re-including the assert header is exactly the kind of misleading behavior that should not be relied on in real code, let alone examples, given that it is the only header defined by C99/C++98 that changes its behaviour with multiple inclusions.
- Nor does this address my concern at all, since you did not override NDEBUG in your example.
- There is no "redefining and re-including" other than the manner in which C++ has been designed. Namely, C++ supports the use of multiple translation units. In some translation units you define NDEBUG, in others you don't. This is how assertions have been used since the dawn of C. It is entirely in the hands of the developer where to keep assertions and where to remove them. This is entirely understood by all. Honestly, have you ever used C++ for serious development?
- I have to conclude now that you are simply negligent of handling such runtime errors.
- A surprising statement, given that you twice removed the check on fclose(), and I twice restored it. Again, my concern here is practical. What are we to do about this example code? On the other hand, your concerns are impractical, as you keep moving the goalpost. You keep taking my statements about the example code and construe them as applying to large-scale software development. You want to make dialogue boxes, handle the possible removal of USB sticks, full hard drives, and God knows what else. It's all out of proportion. The quoted sentence above is ridiculous, and you know it. You are quickly becoming a troll.
- Your confusion over assert(!"bad state") is yet another indication, among others.
- Where did you get the impression that I was confused? I said that it was "an awkward "!" hack to evaluate a string to false". Are you saying that my analysis is incorrect on the purpose? I already made a point on how assertions should be used.
- I got that impression because of your various comments such as "awkward hack", "WTF?", "braindead usage", and "unintelligible". You appear to be confused or surprised by the use of assert(!"foo"), which is a common, useful, and intelligible idiom.
- Your first suggestion was to ignore the error.
- That was not my suggestion at all [...]
- Yes, that was your suggestion, because twice you reverted the code back to ignoring the check.
- My previous post did propose a real suggestion — if the line was supposed to be a "placeholder", then a comment explaining what should be placed there would certainly be warranted, preferably replacing the assertion.
- This is the only useful thing you have said in this whole commotion. To me, it was clear that assert(!"file failed to close") was the placeholder. But to the general audience, an additional comment of "// placeholder -- handle failure" would probably be helpful. Good point.
- Now, why didn't you just put that comment in yourself? Why did you keep removing the check on fclose()? This whole discussion was needless. It appears to revolve around the your ideological position on assertions, an unshakable belief which is impervious to my matter-of-fact statements about how assertions are actually used in real life. Oh well.
- Well, this discussion certainly was needless. As for your comments, I have my reasons and justifications, but it's better just to end this. -- intgr 20:15, 16 April 2007 (UTC)
Summary
[edit]I wish to completely demolish all of these arguments so I don't have to deal with them again.
- Well, if you insist on continuing the "needless" discussion, I'll bite.
- It is unacceptable to ignore system errors.
- I never disagreed with this one, although you keep on putting your words into my mouth on this one.
- Assertions can be used to check runtime errors. The cases entail hardware faults which (for example) may be associated with spontaneously lost file handles. In these cases, an immediate abort is the safest action, otherwise the application can do real damage.
- See the next response.
- I/O errors are not, in themselves, an undefined state, but unusual I/O errors such as lost file handles can indicate an undefined state because they are symptomatic of hardware errors, which impose an undefined state.
- I already thoroughly addressed this. Quoting myself: "The loss of a file handle is by far not the only error that fclose() can return: fclose() is also responsible for flushing the latest changes to the file, which may fail, for example, when the user has pulled out their USB stick too early, or when the HD is full — conditions that are bound to happen in practice, but rarely do during development. [...]" This is my primary concern why assertions inadequately address the situation; I assumed that you would be aware of this, and I have been insisting that fclose returns legitimate runtime errors all along. If you're going to respond at all, I would expect you to pay more attention to what I am saying.
- Assertions can and should be left in certain parts of production/release code.
- Yes; the standard assert() macro does not, however, communicate that when leaving NDEBUG in an uncertain state, like the example code does. So I will take it as just an afterthought/rationalization.
- Assertions can and should be used conditionally within the same build. Parts of the code have assertions enabled, other parts do not. This is how C++ developers use assertions.
- Yes, however, I disagree with the behavior of conditionally defining NDEBUG on a per source-file basis — obviously the same source file may contain inner loops as well as non-bottleneck code; I would use two assertion macros, explicitly making it clear in the source which assertions are compiled into release builds. However, given that this is irrelevant to the question and a matter of preference, I do not think we should debate this.
- C++ is designed to support multiple translation units. In most cases, a translation unit is an invocation of the compiler with a source file as input. Each translation unit is free to define or undefine macros (such as NDEBUG) in addition to any other action as long as the ODR is not violated. This should not be construed as "redefining and re-including", nor is it "misleading behavior that should not be relied on in real code". On the contrary, this is how C++ is designed to work.
- I assumed that you meant conditionally including different assertions in a single source file (which is a real use case per comment above). Naturally, you can have different definitions in different source files. However, this, yet again, is irrelevant; quoting myself: "Nor does this address my concern at all, since you did not override NDEBUG in your example."
- assert(!"something bad") is a common and useful idiom in C and C++.
- A matter of preference. I dislike using practices in examples that are discouraged in real code, but perhaps, with an explanatory comment. I would not expect a reader to be aware of it, given that this is the first time I came across it. I still find it a hackish work-around over the fact that assert() only emits a message when the argument evaluates to false.
- We cannot anticipate how the reader will handle release errors, especially in this simple example. We obviously can't provide code for dialogue boxes, for instance. The current strategy is to provide a placeholder assertion to alert the reader of the release error condition.
- And this is the exact reason we also shouldn't rely on whether the reader's NDEBUG macro is set — we cannot predict their environment or coding habits. An exit() call, given as my first example, would be straightforward and to the point, without relying on the user's environment. Nor would a plain comment, although that simply wouldn't "handle" the error. -- intgr 08:13, 17 April 2007 (UTC)
Xerxesnine 03:18, 17 April 2007 (UTC)
- To summarize the points I have made:
- It is unacceptable to ignore system errors. (ditto)
- fclose() returns important and common runtime errors, even if hardware is not malfunctioning, since it is responsible for flushing dirty buffers.
- Hence, an assertion is inappropriate for addressing fclose() — errors such as "out of space" should never produce a crash, or be considered an "undefined state".
- I believe that example code should not contain patterns that are discouraged in real code, unless they are explicitly explained (those C++ books probably did explain the given assert pattern).
- The standard assert() macro is inadequate for expressing release build assertions. Even if you did specify the state of NDEBUG, it could still be misleading to readers who are not aware of the meaning of NDEBUG.
- -- intgr 08:13, 17 April 2007 (UTC)
Response to Intgr:
- Since it is agreed that ignoring system errors is unacceptable, it is also unacceptable to twice revert the example code back to ignoring system errors.
- fclose() errors are not exclusively due to hardware failure, but can indicate hardware failure.
- fclose() does not return important and common runtime errors. It returns 0 on success, EOF on error. The corresponding errno is EBADF (bad file descriptor). That's all the information that fclose() provides.
- fclose() does not give (nor could it possibly give) a way to distinguish between hardware failures and USB-drive-pulled-out errors. The best thing to do is assume the worst: hardware failure.
- We obviously cannot know how the developer will handle filesystem errors which aren't "hard errors". That is why there is a comment which says, "handle filesystem errors here", the only constructive outcome of this discussion. The worst case scenario is a hardware failure, which is why the assert() is there.
- assert() is exactly appropriate for a hardware failure, since a hardware failure requires immediate termination.
- assert(!"bad state") is not "discouraged in real code". On the contrary, the best experts encourage it, for obvious reasons. It (normally) gives you the file, line number, and the user-defined message; it calls abort(); it produces a hardware breakpoint inside debuggers. Who could ask for anything more?
- exit() should not be called in place of assert(). If we are talking about hardware failure, exit() is a bad idea because it calls the destructors of static objects; it does not halt execution as required.
- The use of assert() entirely adequate; it used frequently, even in release builds. Any developer who is tasked with handling fclose() errors would, by definition, understand how assert() works.
As if this steady stream of misinformation coming from Intgr was not enough, I have counted three outright troll remarks from him. This, together with his two reverts on the fclose() checks, is an firm indication of bad faith.
Xerxesnine 15:37, 17 April 2007 (UTC)
- (digging up old comments) "comments such as "awkward hack", "WTF?", "braindead usage", and "unintelligible"."
- For these comments, I apologise; I realise that I was being uncivil.
- But speaking of fclose errors: In fact, the fclose() implementation in the standard C libraries of both Linux and Windows (these I tested, but very likely other popular OSes as well) returns EOF and sets errno appropriately in case of failures. The MSDN article on fclose is unhelpfully vague, but the Linux man page clearly states "The fclose() function may also fail and set errno for any of the errors specified for the routines close(2), write(2) or fflush(3)."[2], as does BSD's.[3]
- "assert() is exactly appropriate for a hardware failure, since a hardware failure requires immediate termination."
- I do not buy your reasons for assuming the worst case hardware failure.
- If the problem is not fatal to the kernel (e.g., HD failure, some bus failures, connectivity outages and whatnot), then the kernel is fully capable of handling such cases for you, and passing a safe error code to the user space. If the problem is in a component that is fundamentally relied on, e.g., the CPU, RAM, chipset, etc, then it doesn't matter much at all what happens, since you cannot really guarantee anything at that point — and it is much more likely that your application or the OS crashes at a random pointer reference, or corrupts the data in some other way, than be caught in this assertion.
- However, if you assume that it's a runtime error, then you might rescue the user's unsaved work.
- "assert(!"bad state") is not "discouraged in real code"."
- Okay, I do not like it, but I suppose it's just a matter of taste; I will give in.
- "We obviously cannot know how the developer will handle filesystem errors which aren't "hard errors". [...]"
- But we can guide him by pointing out that it's worth handling, rather than asserting that it is not supposed to happen. While having the comment is better than not, the assert still implies to me that it is not a practical error to bother with, and needless to say, I find that implication very misleading.
- "twice revert the example code back to ignoring system errors."
- This is another point that I've addressed beyond absurdity. I am not asking you to embrace or agree with my reasons, I am asking you to acknowledge them. Let me quote my previous responses:
- "Thus, the example was more misleading than useful — leaving the impression that the error was checked, while it really wasn't"
- "Certainly, but misleading (tricking) the reader is not the right way to do it."
- "I suggested not to include a piece of code that I considered misleading and a cookbook example of an assert anti-pattern."
- "As if this steady stream of misinformation coming from Intgr was not enough, I have counted three outright troll remarks from him."
- If I'm a troll then why do you keep biting? If you want to withdraw, I won't mind, and I agree that having this argument is a waste of time. I even tried to end this argument by swallowing my pride and not responding to your comment, which I nevertheless disagreed with. But you insisted on pulling me back in, I will not let you kick me around without defending myself.
- As for "misinformation", please point out where I have lied to you, or misinformed you? Sounds like a resort to ad hominem.
- -- intgr 20:02, 17 April 2007 (UTC)
It turns out I misread fclose() even after re-checking it. At http://www.die.net/doc/linux/man/man3/fclose.3.html for example, it lists EBADF, which seems to imply that it's the only relevant error code. The next sentence is "The fclose() function may also fail and set errno...", but I read that as "The fclose() function may fail to set errno...", which would explain why only EBADF was listed, in my mind. I put these together and thought the ISO C standard only set a single failure flag for fclose(), leaving the rest to the particular platform API.
So it turns out the fclose() indeed sets useful errnos. I can now imagine legitimate code which checks standard ISO errons before the assert() happens, whereas before I believed one could only use the platform API before the assert(). While I would still put an assert() when all other checks fail or are bogus, that assert() seems much farther away now, and this changed my mind about it.
I apologize to Intgr for wasting his time on this. Mea culpa.
Xerxesnine 01:29, 18 April 2007 (UTC)
- Thanks! Let's forget about this and have a a nice cup of tea and a sit down. :) -- intgr 20:49, 18 April 2007 (UTC)
Boy this Xerxeresnine character was a jerk, and incompetent too. You should never ever ever use the assert macro for anything other than logic errors. -- 98.108.208.207 (talk) 13:25, 15 July 2008 (UTC)
Indeed. That exchange shows him to be an incompetent moron. Let's hope none of his code has killed anyone. -- 71.102.128.42 (talk) 02:57, 17 November 2014 (UTC)
A very misleading name
[edit]I don’t know who invented the term, but it’s highly misleading: RAII deals neither with resource acquisition nor with initialization. RAII is automatic resource releasing upon leaving scope, whether via throw, break, continue, return or by control reaching the end of block. As in the following code:
{ std::auto_ptr<X> x; // initialization doSomething(); // If this throws, auto_ptr::~auto_ptr does nothing dangerous. x.reset(new X); // Resource acquisition. Done by programmer and not by RAII class! doSomethingElse(); // If this throws, the X object is released. // If nothing throws, ‘x’ is released here. }
Roman V. Odaisky 13:02, 6 June 2007 (UTC)
Since your example does not acquire resources during initialization, it does not demonstrate "resource acquisition is initialization", or RAII. I believe Stroustrup introduced the term in his 3rd ed. book, where he gives (something similar to) this example:
class log { database_lock lock ; file output ; public: log( database_handle db ) : lock(db), output("output.txt") { } } ;
If the lock fails, an exception is thrown and the opening of "output.txt" is never attempted. If the lock succeeds but the output file fails to open, then an exception is thrown which results in the the release of the lock. If both succeed, and an exception is thrown at some point during the scope of the log instance, then both resources are released.
The strategy guarantees that resources are acquired and released in the correct order (FILO/LIFO) and that objects are always in a valid state. If an object cannot be created in a valid state, that is, if the object cannot acquire the needed resources, then we cease the current execution by throwing.
In your example, you have elected to construct an invalid object (any use of auto_ptr is undefined until it is given a valid pointer). This positively demonstrates what RAII seeks to remedy. Xerxesnine 17:43, 21 June 2007 (UTC)
- I can't agree with you. RAII is all about releasing resources, not acquiring them. Why Java has no RAII? It has constructors, it has exceptions; but it lacks destructors.
- As for LIFO order, I consider such a constructor bad style because its behavior depends on the order in which member variables are defined in the class (see 12.6.2/5), someone could break it by merely regrouping members.
- The default constructor of std::auto_ptr initializes it with NULL: explicit auto_ptr(X* p = 0) throw();
- To repeat, RAII is automatic resource releasing upon leaving scope. Why “RAII is a key concept for writing exception-safe code” (taken from the article)? It is because destructors are guaranteed to run however control exits the scope.
- So, Wikipedia being not a forum, how can we best reflect this in the article? Roman V. Odaisky 18:46, 29 July 2007 (UTC)
- Yes, "automatic resource releasing upon leaving scope" is part of RAII. But despite your multiple assertions otherwise, that is not all of it.
- Repeating your own definition of RAII will not persuade others to adopt it over Stroustrup's definition. Stroustrup called it RAII for a reason. It is about acquiring resources, hence the name "resource acquisition is initialization". If you redefine "resource acquisition is initialization" as something which "deals neither with resource acquisition nor with initialization", then I can certainly see why you might think the name RAII is misleading.
- FILO ordering is an essential part of RAII. Locks, mutexes, and most (all?) resources follow FILO. RAII ensures FILO order automatically; it does not rely upon the diligence of programmers to do it. You cite 12.6.2, which explains this important mechanism with regard to member data. The purpose of RAII is to take advantage of that mechanism. It is not bad style; the style is called RAII. Please see Stroustrup's example in his 3rd ed. book, "The C++ Programming Language". Really, please look at that example and its explanation before responding. It appears that you are simply missing the point.
- Of course the default constructor of std::auto_ptr initializes internally to NULL. It is not clear why you believe that is relevant to mention here. Or were you being a stickler for the definition of "invalid object"?
std::auto_ptr<int> a ; *a = 1 ; // crash
- That is the behavior I wished to convey. It's not good behavior. Call it what you wish.
- You are correct that Wikipedia is not a forum. It is especially not a forum for proselytizing a new definition which is contrary to the canonical definition used by the creator of the language.
Construction, Destruction, and RAII
[edit]The tr1::shared_ptr (or if you prefer boost::shared_ptr), taken as an example, is an example of RAII for which resource acquisition doesn't necessarily occur when the constructor is called, and resource relinquishment doesn't necessarily occur when the destructor is called. That RAII is about acquiring a resource in the constructor and releasing it in the destructor is merely a lie to children. What RAII is really about is that the object is responsible for the management of a resource, and its destruction should guarantee its cleanup, which is quite distinct. —The preceding unsigned comment was added by 75.130.108.174 (talk • contribs) 15:10, 8 August 2007.
- Please justify your bold statements by responding to the points I made on the subject above. Xerxesnine 19:00, 8 August 2007 (UTC)
- I don't know which of your claims you are referring to, but I don't think it's appropriate to ask you to be more specific, because I disagree with your criteria for conflict resolution. This is an encyclopedic article, not a home page. We shouldn't be deciding what RAII is, we should be documenting what it is. Though I'd imagine both of us are industry professionals, neither of us are authoritative sources.
- Therefore, for the article, I claim that we should be referring to facts and citations (subtle difference), where a fact is encyclopedic in nature--that is, if something is claimed to be true by an authoritative source, it should automatically be treated as true (unless redacted). If two authoritative sources disagree--no problem. Citing the controversy is certainly an option.
- If this doesn't sound fair to you, please let me know by which criteria we can resolve this dispute.
- Following my own rules, here is my defense:
- TC++PL3, 14.4.1:
The technique for managing resources using local objects is usually referred to as ''resource acquisition is initialization.'' This is a general technique that relies on the properties of constructors and destructors and their interaction with exception handling.
- This is the section most closely matching the example you cited (which is slightly different in TC++PL3--if it helps, I could quote the exact example). Nothing here is being defined, just referred to. Nothing here specifies when resources are acquired or deleted, only that RAII relies on the relationship between constructors, destructors, and exceptions. So, if this is what you were referring to, it's incorrect. Still, since you weren't specific, I leave it open that you may be referring to something else (you're more than welcome to cite what--Stroustrup has a lot to say about RAII throughout 14.4). For the record, various miscellaneous references suggest that the concept of RAII was introduced in "Design and Evolution of C++", which unfortunately I don't have (since I didn't cite which, feel free to treat this as an unverified claim, though I honestly don't see where it would be controversial, especially since you seem to recognize Stroustrup as an authority).
- It's a fact that the auto_ptr supports RAII (per 14.4.2 TC++PL3). But auto_ptr's have no constructors that obtain the resources they manage (ISO IEC 14882-2003 20.4.5). Take a look at a typical example:
auto_ptr<int> x(new int(5));
- Note that the resource, the heap int object, is allocated before x's constructor is called, not in the constructor.
- It's a fact that tr1::shared_ptr is an RAII object (per Meyers, EC++3, Item 13 (p. 66)). It should be fairly obvious that a shared_ptr destructor doesn't necessarily relinquish the resource, but rather, it's just responsible for relinquishing it. In particular, shared_ptr destructors only relinquish the resource when the last shared_ptr is destructed. This implies that ownership is the key concept behind RAII, and indeed...
- It's a fact that the key concept behind RAII is assigning ownership of a resource to a scoped object (Stroustrup, http://www.research.att.com/~bs/except.pdf (p. 3)):
The basic tools for writing exception-safe code are: ... (2) the support for the ''resource acquisition is initialization'' technique. The key idea behind the ''resource acquisition is initialization'' technique/pattern (sometimes abbreviated RAII) is that ownership of a resource is given to a scoped object. Typically, that object acquires (opens, allocates, etc.) the resource in its constructor. ...
- Note in particular the qualifier typically that Stroustrup uses. Furthermore, note that this is precisely what I said RAII was about earlier.
- Is this adequate?
- References are intentionally informal (I don't want to add ref tags on a discussion page). TC++PL3 is "The C++ Programming Language, 3d ed", EC++3 is "Effective C++, 3d ed", both are Addison-Wesley. 14882 should be obvious.
- Rephrasing definition again... because my changes will probably be controversial, I'll explan. "Variables" changed to "scoped objects" because that's what Stroustrup says in the exception article (see above, and also cited in main body), and not all scoped objects are bound to variables (consider object temporaries). I don't feel the first statement was inaccurate though (except, again, the variables part).
- Also, RAII is not merely allocating in the constructor and deallocating in the destructor (see same Stroustrup article again; reread above if you got opinions from TC++PL, which isn't wrong either). It's more comprehensive than this. Also see above--RAII is often used (I say "often" because smart pointers are common) to manage resources created before construction, as opposed to in the constructor.
- Still, my wording is probably not perfect, and I'm not quite sure if we care for an encyclopedic reference to be this precise, but the old definition was wrong (where wrong I mean, "officially" wrong, not mere opinion--see above), if only technically, so at least this fix deserves peer review.75.130.108.174 07:45, 2 September 2007 (UTC)
PHP5
[edit]PHP5 introduces destructors. Does anyone know if they are guaranteed to run in a timely fashion, for example, when a static object passes out of scope? If so, then this technique could be used in PHP5. Either way, the article should mention whether or not the technique is applicable to that language. - Jmabel | Talk 17:37, 21 February 2008 (UTC)
- This article would suggest that it works, starting with PHP 5.2, but I don't know how definitive it is. - Jmabel | Talk 17:40, 21 February 2008 (UTC)
GNU C
[edit]GNU C adds a cleanup attribute that helps plain C code in exceptional cases. —Preceding unsigned comment added by 17.201.20.87 (talk) 19:24, 27 February 2008 (UTC)
"automatic, deterministic"
[edit]I believe better wording to replace "the automatic, deterministic destruction" would be "the implicit, synchronous destruction". I believe "automatic" is not as informative as "implicit" and "deterministic" is not as informative as "synchronous". In fact, I think "deterministic" can be misleading because the destructor could be implemented with non-deterministic characteristics (such as calling fclose, which does not have a deterministic result). But I get the impression someone loves the existing wording, since it also shows up in the Java vs. C++ article, so I would like your comments. --pfunk42 (talk) 05:25, 17 May 2008 (UTC)
C# has had destructors since 1.1
[edit]See [4]. So one could use the exact same form of the c++ RAII pattern with c#. 64.234.67.2 (talk) 05:53, 29 December 2008 (UTC)
- Furthermore, any class implementing the IDisposable interface, has an automatic, deterministic destruction when it leaves a using scope (IDisposable.Dispose is called). So using the C++ example in the article, the C# would look something like this:
public class MyFileWriter : IDisposable { private StreamWriter _swriter; private StreamWriter Writer { get { return _swriter; } set { _swriter = value; } } public MyFileWriter (string filename) { try { Writer = new StreamWriter (filename); } catch (Exception ex) { throw new ApplicationException ("file open failure", ex); } } public void Dispose () { try { Writer.Close (); } catch (Exception ex) { // handle it } } public void Write (string str) { try { Writer.Write ("{0}\n", str); } catch (Exception ex) { throw new ApplicationException ("file write failure", ex); } } } class TestMyFile { public static void Main() { using (MyFileWriter logfile = new MyFileWriter ("logfile.txt")) { logfile.Write ("hello logfile!"); } } }
- Why is this not RAII? 24.243.3.27 (talk) 02:48, 7 January 2009 (UTC)
- I guess one could argue that this is a form of RAII, but it seems this is very uncommon in C#, as opposed to C++, where it is a common idiom. decltype 05:45, 9 January 2009 (UTC)
- I'm not sure how common it is (I don't have any usage statistics), but it is definitely used in production. A simple grep -RI 'using(' * | wc -l on a partial mono HEAD checkout (I'm too lazy to pull everything) shows 24 uses of IDisposable objects in using clauses. And I've seen it used many times in the wild. 24.243.3.27 (talk) 08:41, 10 January 2009 (UTC)
- Fair enough. I noticed that it is in fact briefly mentioned in the article as well. decltype 09:23, 10 January 2009 (UTC)
- You cannot implement RAII in C# with destructors alone, because destruction is not deterministic. You would have to do something similar to the example by 24.243.3.27, using using and Dispose to achieve deterministic destruction. decltype 05:45, 9 January 2009 (UTC)
- I'm not sure of the original / intended meaning of RAII, but taking the expression literally (and its corollary, Resource Release Is Uninitialization)--it says nothing about any deterministic reasoning about the *time* when the resource is released, it simply guarantees that *whenever* the object is uninitialized, the resource is also released. This, of course, may defeat the purpose of why some or many (most?) developers employee RAII (i.e., in order to reason about resource consumption at any given time), but it really seems like a more apropos name should be used in that case; some thing like "Automated Deterministic Resource Acquisition" (forgive me, I'm not the most imaginative person). 24.243.3.27 (talk) 08:41, 10 January 2009 (UTC)
- I've always found the name rather puzzling, but if it's not deterministic destruction, it's not RAII. This is also implied in the article several times. decltype 09:23, 10 January 2009 (UTC)
- There is much more to RAII than simply the pseudo-destructors of C#. RAII is about lifetime management and automation. When an object that contains another object gets destroyed, the contained object must have it's cleanup done as well - prior to final destruction of the container. That order is important because "resources" also mean things like "callback registrations". If your method is called after you are destroyed, you have not cleaned up your resources. "Using" does not even apply to object containment - it applies to scope lifetimes. Event lifetimes (asynchronous calls to an interface) and state machines (where state objects define the lifetime of the state and all objects held by the state object also live for that time) cannot be used with automation - you have to explicitly dispose all members. This article should not be a defense on why c# almost-kinda-maybe has RAII, but what real RAII means as far as benefits to language design and code implementation.Ex0du5 5utu7e (talk) 20:29, 8 May 2014 (UTC)
Mutability section should be removed
[edit]Googling "mutable RAII" gives 7 hits, many of which are derivatives from this Wikipedia article. "Immutable RAII" fares a little better, giving 171 hits. Some of the hits are for Wikipedia article, some are to discussions where Matthew Wilson, author of the term, is using it. Or to excerpts from Matthew's books. Clearly the term has not caught on, and should not be in Wikipedia. I'll go ahead and remove the mutability section if there's no objection. Matonen (talk) 13:45, 15 April 2009 (UTC)
SBRM
[edit]Do you guys think the acronym ```SBRM``` (scope-bound resource management) deserves to be included here too? It seems to be more descriptive of what happens here. I can't seem to track down the origin with quick googling though - just various blogs and forums, starting about 2007 and getting more common by 2010. --Cubbi (talk) 18:06, 25 August 2010 (UTC)
"Resource management without RAII" section needs to go
[edit]The RAII concept can be understood with just the C++ example given. "Resource management without RAII" is an ever-growing section with everyone adding a remark or example about their favourite language, and the new "Ad-Hoc Mechanisms" section is longer than the C++ example itself (not to mention original research). --Matonen (talk) 14:20, 20 October 2010 (UTC)
- Please don't remove the "Ad-Hoc" section! That was exactly what I was wanting when I looked up the article. If you want to clean it up, you might use http://code.google.com/p/libex/ as a reference for what is now presented as original research. (-Michael)
- I fail to see the connection between libex and RAII. Libex provides a pseudo-exception mechanism having "finally". IMO explicit cleanup in a finally block is the opposite of what is considered as RAII by the majority of programmers. As is the the so called "RAII pattern in C", which is a lot of gotos and centralized cleanup at the end of function. Don't get me wrong, it a good technique and I use it a lot myself, but it's not RAII. - psevon (talk) 19:33, 10 April 2014 (UTC)
- I second its removal, or at least heavy pruning. If kept, it needs rewriting to be less needlessly "clever" (e.g. "undesired, 100% file compression" instead of "data loss").—LionsPhil (talk) 02:26, 12 April 2011 (UTC)
Resource management & composition.
[edit]This article, especially the comparison to other forms of resource management is incomplete without a section that delves into the subject of compositional properties of the different resource management technologies. The single most compelling property of RAII when compared to try/finally lies in how RAII works with 'deep' resources. In a pure GC+try/finally world, 'being a resource' is transitive when used in composition. In a RAII world it isn't. If in a GC+try/finally world an object contains a 'deep' resource, than this object requires manual resource management at the level of the user of the composite object. With RAII the resource (and thus the resource management) is fully captured at the deeper levels of composition and neither the higher levels of composition nor the user of the composite object are exposed to the fact that the there is a deep resource in there. That is, RAII provides the possibility of encapsulation of resources, while for the alternative, composition will brake encapsulation.
One thing that aggravates the problem for alternative resource management technologies (GC+try/finally) is polymorphism. Combined with polymorphism, not only 'holding' a deep resource makes a composed object require manual resource management, but also composition using any polymorphic component will turn a composite object into an object needing manual resource management. As RAII doesn't have the encapsulation problems it also does not have the polymorphism problems that GC+try/finaly has. —Preceding unsigned comment added by Pibara (talk • contribs) 09:22, 11 May 2011 (UTC)
Given that there was absolutely no response to the above, I'vv added a section "Disadvantages of scope bound resource management alternatives" to the page, hope this is OK. Pibara (talk) 07:40, 5 July 2011 (UTC)
- A problem with the added section is that it looks like your own research/analysis. Being an encyclopedia, WP does not publish original thought - all content should be sourced to reliable references. This article already has problems in this respect. Regards,
decltype
(talk) 10:17, 5 July 2011 (UTC)
Copying and assignment
[edit]The article seems to assume that RAII is only about scope-based automatic construction and destruction of objects. It omits an extremely important part of RAII, namely copying and assignment (which in C++ is implemented in the form of copy constructors and copy assignment operators). This is quite obvious as the article gives as examples of "RAII" the gcc extension "cleanup" and the Java "finally" block. That's only part of RAII. Those do not handle copying and assignment properly.
Copy semantics is what allows, for example, returning objects from functions by value, giving objects as value-based function parameters, copying objects (obviously), creating entire arrays of objects (each element being default-initialized or initialized as a copy of an existing object) and so on, while still fully adhering to the RAII mechanism. This is most certainly not covered by "finally" blocks or a "cleanup" attribute.
Another aspect that's missing is that RAII works in C++ even on the heap: As long as the container structure is allocated and deallocated properly, the contained objects will follow the RAII rules appropriately. (This is the reason why, for example, the standard library data structures do not need to know how their elements are copied and moved around, as RAII takes care of that.)
Comparing RAII to Java's "finally" blocks is, at the very least, misleading. — Preceding unsigned comment added by 84.249.92.156 (talk) 06:25, 12 April 2012 (UTC)
RAII in C#
[edit]The article seems to imply that there is no RAII in C#, but this is not true: it merely works in a different way, with more explicit syntax, as already described in this talk page (see C# example above).
This has nothing to do with C# not having deterministic memory deallocation, as RAII is not fundamentally about memory allocation. For example, this C# example is RAII, since reader and writer objects are automatically disposed (i.e. closed) upon exit from the using scope. 212.92.211.195 (talk) 14:32, 30 January 2013 (UTC)
- The article clearly mentions the dispose pattern as an alternative for languages without determinstic memory deallocation. The dispose pattern serves as a good alternative to RAII in many situations, but it can only be used for shallow resources within a method. One e.g. still have to explicitly implement the Dispose method and manually close all resources belonging to a class when developing own classes in C#. This is not the case for C++ with RAII. --Fredrik Orderud (talk) 16:03, 30 January 2013 (UTC)
- C# does not have RAII for expression lifetime objects or for state lifetime. It did not have scope-based until the using clause was added, and this failed to actually add full RAII because it did not address the other cases. C# does not automatically cleanup resources contained by other resources. Additionally, the object dtor will still be accessed after the dispose, and this multistage cleanup leaks referentiality and, if the advice given by MSDN on proper dispose/dtor use is followed, requires increased complexity. There are a number of ways in which the C# using (and Java try-with-resources) does not provide the complete automation and referentiality assurances that RAII as a concept intended. It is true, though, that C# has deterministic scope cleanup, but that is discussed elsewhere on Wikipedia. Ex0du5 5utu7e (talk) 22:35, 3 December 2014 (UTC)
RAII article in mid 2013 less useful than in mid 2012.
[edit]Through 2012 September, The RAII entry spent more text on usefully showing the bonding together of creating a data object and acquiring auxiliary resources, and showing how falling out of scope could release those auxiliary resources. It had examples in C++, Java, Ruby, Python, and Smalltalk which all essentially showed RAII support (despite the section being conflictingly titled "Resource Management without RAII"), using library calls and closure blocks which all essentially show that the languages can trigger the destructor when needed.
The fact that the first mentioned use for RAII in the article was given as mutex locks is fairly esoteric for some programmers, but the C++ example does show two implicit examples of RAII through C++ features, one for the mutex and one for the file.
The "GCC extensions for C" subsection, about autodeallocation, which is related to RAII despite being in the wrong section, only covers deallocation on falling out of scope. Although useful, it would be more edifying in a complete example showing the allocation part as well, as the old, long C example did (up to 2012 September or so) with the RAII macro automatically calling the destructor for the C object at the end of the scope. Extract:
#define RAII(t, v, a,...) \
for(int _outer_once = 1, _inner_once = 1, _jumped = 0 ; _outer_once-- ; \
_jumped && (Throw(_jumped), 1), 1) \
for(_jumped = setjmp(_jmp_bufs[_jmp_i++]) ; _inner_once-- ; ) \
for( t *v = t ## New(a, __VA_ARGS__) ; v ; t ## Delete(&v))
[...]
RAII(File, from, "file-from", "r+") { /* r+ to ban opening dirs */
RAII(File, to, "file-to", "w") {
int c;
while(EOF != (c = FileGetc(from)))
FilePutc(to, c);
puts("copy complete");
}
}
Note that although that code does use variadic macros and some recent standardized C features, it's not strictly confined to GCC, whereas the RAII_VARIABLE macros appears to be far less portable.
That "Ad-hoc mechanisms" section was in one key way one of the most interesting (alert: I'm biased, I wrote it, and I agree shorter would be nice, but it's not easy to accomplish) because instead of just using a language feature that implements key RAII components implicitly, it instead had to *explicitly* show how the initialization was part of the declaration, and how the destructor was called even in the face of errors.
Renaming that section from "Ad-hoc mechanisms" to "Implementing RAII in non-RAII Languages" would really have been a more reasonable choice than simply claiming "ad-hoc work-arounds are in general of little encyclopedic value" and tossing it.
Further, it also addressed a lot of the tradeoffs involved in retrofitting RAII to languages without direct support, the benefits achieved with RAII, and so on. That part of the article is still available in its original form at http://www.talisman.org/~erlkonig/documents/ad-hoc-exceptions-in-c/
(Aside; the the second C example was inferior, essentially being an example of a way to avoid having deeply-nested blocks to handle failures. While common in kernel code, it was not RAII - the uninitialized FILE pointers and reliance on specific resource-release calls have nothing to do with RAII. Dropping that one made sense.)
So I'm not very happy with what's left of the RAII entry, but order to work around what bias I might have, I'd like to leave it up others going forward.
C. Alex. North-Keys (talk) 21:52, 5 August 2013 (UTC)
This, like the cleanup extension in gcc, seem to address only automatic destruction of local-scope resources. Although that's one part of RAII, at least I consider smartpointers as another equally important part, allowing scopes to give out allocated resources, but still have them subject to automatic destruction. The only "real" RAII I have seen done in C is my implementation of smartpointers (https://github.com/psevon/exceptions-and-raii-in-c). It provides functionality similar to unique_ptr and shared_ptr in C++ (not weak_ptr at the moment, but it can be easily added if necessary), allowing shared or unique ownersip to the constructed resources, and transfer of ownership to outside the scope, even to another thread. It keeps track of things and initiates cleanup by macros replacing the braces, and macro replacement for return. It works by keeping a cleanup stack in the heap memory, which makes it possible to use setjmp/longjmp based exception mechanism, or exit a scope without calling the correct macro and still have all the resources from the unwound scopes implicitly destructed, in the catch block or when doing the next clean exit from a scope. — Preceding unsigned comment added by Psevon (talk • contribs) 19:22, 10 April 2014 (UTC)
object lifetime: resource allocation
[edit]In the sentence "In RAII, holding a resource is tied to object lifetime: resource allocation (acquisition) is done during object creation...", should the colon after "lifetime" be a period or a semicolon? — Preceding unsigned comment added by 188.126.200.132 (talk) 19:31, 11 July 2014 (UTC)
Non-stack-based RAII (asynchronous event RAII and other)
[edit]The line
"Ownership of dynamically allocated objects (memory allocated with new in C++) can also be controlled with RAII, such that the object is released when the RAII (stack-based) object is destroyed."
is incorrect as written because of the "stack-based" modifier. It could be made "more correct" by simply removing the modifier, but it still misses the point because it is not just "dynamically allocated objects" that can be managed, and you typically do not want dynamic allocation unless you are explicitly not stack-based. The only time you use dynamic allocation with scope lifetime is when you need more memory than the stack can allow or need special memory permissions, both fairly rare.
When you have an interface that can receive asynchronous events, though, and you need something to have a lifetime that lasts longer than the first event scope, that's when you typically need dynamic allocation. This is what State Machines were created for. State Machines are RAII for asynchronous events. A State is what manages the automated lifetime between the two events. Everything the State holds (everything contained in the object) is automatically created when the State is created and destroyed when the State is destroyed. This solves exactly the same problems that scope based RAII solves:
- Name referentiality matches lifetime. There are no zombie objects (objects that aren't fully "live" but aren't completely "dead" and unreachable either) or multi-stage construction or destruction. If you can reference the name of the object (within the State in the Machine pattern, or within the scope in the traditional), then it is fully usable.
- Automation of management and cleanup. There is no possibility of mismatch between create and destroy code, where resources leak creates or double destroy. When you add the ownership relation, the cleanup is inserted by the compiler. You only need one expression, not two, to declare the lifetime intent, so divergence is not possible.
In fact, there are actually four lifetimes, all of which are managed by RAII:
- Expression lifetime - anonymous objects created during the evaluation of an expression are automatically cleaned up at the end of the expression evaluation. This is used extensively in proxy objects, stream formatters, expression templates, and numerous other common idioms of RAII languages. You will note that the controversial languages that do not inherently support RAII but support "similar mechanisms" (like C# or Java) do not support this in an automated fashion, which is one reason there is controversy with such labelling.
- Scope lifetime - what the article mostly discusses.
- State lifetime - mentioned above, between asynchronous events.
- Application lifetime (or, better, translation unit lifetime) - file scope objects that are created by the language rules for file-scope objects and destroyed on program exit (in the atexit cleanup, for instance, in c++).
In all these cases, referentiality equals lifetime and management is automated by the simple declaration. That is RAII, and a good article should mention these things. Ex0du5 5utu7e (talk) 22:23, 3 December 2014 (UTC)
Error in C++ example
[edit]Perhaps I'm missing something, but there appears to be a serious error in the C++ coding sample: the destructor of the static mutex instance will not be called until the program exits. The static variable may not be visible outside the scope of the function in which it resides, but it is nevertheless global. So if we rely on the destructor to unlock the code, then the mutex will remain locked after the first call.
It looks to me that to make this function work would remove most of the RAII characteristics that it is trying to demonstrate.
Maybe a better example of RAII would be a function that returns an instance of an object? I.e., the called method allocates the instance on the stack, automatically copies the instance to the return value, which is deleted when it goes out of scope in the calling routine.
Rstinejr (talk) 13:39, 29 December 2014 (UTC)
Yes, why is the mutex static? It is my believe that that will give the mutex a life-time longer than the procedure's stack-frame. Thus, the destructor will not execute when the file no longer needs exclusive access. I'm going to change the example, and see if anyone complains. --129.33.132.9 (talk) 18:30, 13 May 2015 (UTC)
The mutex need to be static to prevent multiple threads from calling the function simultaneously. That would lead to concurrent access to the same file. RAII still applies to the lock object, which is not static. Fredrik Orderud (talk) 19:40, 13 May 2015 (UTC)
Thanks Fredrik Orderud! 😃 172.56.39.30 (talk) 16:34, 23 May 2015 (UTC)
There is problem with the static mutex. The static mutex is being constructed when the function is invoked the first time. This is fine as long as it is single threaded. I don't know if there is anything in the C++ standard, which guaranties that such a function can be invoked from multiple threads at the same time. Moving the mutex into the global scope would solve this. See also https://stackoverflow.com/questions/6915/thread-safe-lazy-construction-of-a-singleton-in-c PF~~ — Preceding unsigned comment added by 139.181.28.34 (talk) 21:18, 9 October 2017 (UTC)
C++11 and newer guarantees that static variables are initialized in a thread-safe manner (http://en.cppreference.com/w/cpp/language/storage_duration#Static_local_variables). Therefore, the example is safe & correct. Fredrik Orderud (talk) 16:03, 10 October 2017 (UTC)
Is it a Programming idiom?
[edit]Does anyone have WP:V sources that can confirm this? Ushkin N (talk) 17:26, 30 May 2016 (UTC)
- There is Sutter, Herb; Alexandrescu, Andrei (2005). C++ Coding Standards. C++ In-Depth Series. Addison-Wesley. p. 24. ISBN 0-32-111358-6. – Tea2min (talk) 06:23, 31 May 2016 (UTC)
- If it is a programming idiom, why is it enlistet as a software design pattern? (At a higher level there are architectural patterns, and below software design patterns, there are idioms.)--Sae1962 (talk) 16:05, 28 November 2016 (UTC)
Other names for this idiom
[edit]I haven't found the cppcon talk calling it DRES (Destruct Resources on Exit Scope) yet but here is one reference in the meantime: C++ mingw g++ and Notepad++ — Preceding unsigned comment added by JeffyTheDragonSlayer (talk • contribs) 09:07, 9 January 2020 (UTC)
- Indeed. And when searching the interwebs for "Destruct Resources on Exit Scope" the only other source I found was CppCon 2017: John D. Woolverton "C Pointers", referencing the video and DRES is indeed mentioned at 2:34, and even here the abbrevation is explained. Hm. Not sure if that means it's a "common other name" though. -- Evilninja (talk) 09:24, 9 January 2020 (UTC)
Start a discussion about improving the Resource acquisition is initialization page
Talk pages are where people discuss how to make content on Wikipedia the best that it can be. You can use this page to start a discussion with others about how to improve the "Resource acquisition is initialization" page.