summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorMatt Kline <slavik262@gmail.com>2014-10-17 00:57:32 -0700
committerMatt Kline <slavik262@gmail.com>2014-10-17 01:04:37 -0700
commitfbf3c6d588cf39525afd23cb01230a997440dfcf (patch)
treea2131abfecd58f759764d030e7850a009791749d
parenteadecf8b95eba4afec302c0ab49b0ca0ca921299 (diff)
Add C++ section about RAII
Future contributions will include standard library containers and C++11 features.
-rw-r--r--c++.html.markdown130
1 files changed, 129 insertions, 1 deletions
diff --git a/c++.html.markdown b/c++.html.markdown
index 4a070d95..dbca751f 100644
--- a/c++.html.markdown
+++ b/c++.html.markdown
@@ -263,7 +263,7 @@ public:
// Along with constructors, C++ provides destructors.
// These are called when an object is deleted or falls out of scope.
// This enables powerful paradigms such as RAII
- // (http://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization)
+ // (see below)
// Destructors must be virtual to allow classes to be derived from this one.
virtual ~Dog();
@@ -427,6 +427,134 @@ catch (const std::exception& ex)
std::cout << "Unknown exception caught";
throw; // Re-throws the exception
}
+
+///////
+// RAII
+///////
+
+// RAII stands for Resource Allocation Is Initialization.
+// It is often considered the most powerful paradigm in C++,
+// and is the simple concept that a constructor for an object
+// acquires that object's resources and the destructor releases them.
+
+// To understand how this is useful,
+// consider a function that uses a C file handle:
+void doSomethingWithAFile(const char* filename)
+{
+ // To begin with, assume nothing can fail.
+
+ FILE* fh = fopen(filename, "r"); // Open the file in read mode.
+
+ doSomethingWithTheFile(fh);
+ doSomethingElseWithIt(fh);
+
+ fclose(fh); // Close the file handle.
+}
+
+// Unfortunately, things are quickly complicated by error handling.
+// Suppose fopen can fail, and that doSomethingWithTheFile and
+// doSomethingElseWithIt return error codes if they fail.
+// (Exceptions are the preferred way of handling failure,
+// but some programmers, especially those with a C background,
+// disagree on the utility of exceptions).
+// We now have to check each call for failure and close the file handle
+// if a problem occurred.
+bool doSomethingWithAFile(const char* filename)
+{
+ FILE* fh = fopen(filename, "r"); // Open the file in read mode
+ if (fh == nullptr) // The returned pointer is null on failure.
+ reuturn false; // Report that failure to the caller.
+
+ // Assume each function returns false if it failed
+ if (!doSomethingWithTheFile(fh)) {
+ fclose(fh); // Close the file handle so it doesn't leak.
+ return false; // Propagate the error.
+ }
+ if (!doSomethingElseWithIt(fh)) {
+ fclose(fh); // Close the file handle so it doesn't leak.
+ return false; // Propagate the error.
+ }
+
+ fclose(fh); // Close the file handle so it doesn't leak.
+ return true; // Indicate success
+}
+
+// C programmers often clean this up a little bit using goto:
+bool doSomethingWithAFile(const char* filename)
+{
+ FILE* fh = fopen(filename, "r");
+ if (fh == nullptr)
+ reuturn false;
+
+ if (!doSomethingWithTheFile(fh))
+ goto failure;
+
+ if (!doSomethingElseWithIt(fh))
+ goto failure;
+
+ fclose(fh); // Close the file
+ return true; // Indicate success
+
+failure:
+ fclose(fh);
+ return false; // Propagate the error
+}
+
+// If the functions indicate errors using exceptions,
+// things are a little cleaner, but still sub-optimal.
+void doSomethingWithAFile(const char* filename)
+{
+ FILE* fh = fopen(filename, "r"); // Open the file in read mode
+ if (fh == nullptr)
+ throw std::exception("Could not open the file.");
+
+ try {
+ doSomethingWithTheFile(fh);
+ doSomethingElseWithIt(fh);
+ }
+ catch (...) {
+ fclose(fh); // Be sure to close the file if an error occurs.
+ throw; // Then re-throw the exception.
+ }
+
+ fclose(fh); // Close the file
+ // Everything succeeded
+}
+
+// Compare this to the use of C++'s file stream class (fstream)
+// fstream uses its destructor to close the file.
+// Recall from above that destructors are automatically called
+// whenver an object falls out of scope.
+void doSomethingWithAFile(const std::string& filename)
+{
+ // ifstream is short for input file stream
+ std::ifstream fh(filename); // Open the file
+
+ // Do things with the file
+ doSomethingWithTheFile(fh);
+ doSomethingElseWithIt(fh);
+
+} // The file is automatically closed here by the destructor
+
+// This has _massive_ advantages:
+// 1. No matter what happens,
+// the resource (in this case the file handle) will be cleaned up.
+// Once you write the destructor correctly,
+// It is _impossible_ to forget to close the handle and leak the resource.
+// 2. Note that the code is much cleaner.
+// The destructor handles closing the file behind the scenes
+// without you having to worry about it.
+// 3. The code is exception safe.
+// An exception can be thrown anywhere in the function and cleanup
+// will still occur.
+
+// All idiomatic C++ code uses RAII extensively for all resources.
+// Additional examples include
+// - Memory using unique_ptr and shared_ptr
+// - Containers - the standard library linked list,
+// vector (i.e. self-resizing array), hash maps, and so on
+// all automatically destroy their contents when they fall out of scope.
+// - Mutexes using lock_guard and unique_lock
```
Futher Reading: