Resource Acquisition Is Initialization (RAII)
RAII is a strange name for a simple but awesome concept. Better is the name Scope Bound Resource Management (SBRM). The idea is that often you happen to allocate resources at the begin of a block and need to release it at the exit of a block. Exiting the block can happen by normal flow control, jumping out of it, and even by an exception. To conver al lthese cases, the code becomes more compicated adn redundant.
Just an example doing it without SBRM:
void foo(){
resource *r = allocate_resource();
try {
// something, which could throw
}
catch(..) {
deallocate_resource(r);
throw;
}
if(...) {return;} // oops, forgot to deallocate
deallocate_resource(r);
}
As you see there are many ways we can get pwned. The idea is that we encapsulate the resource management into a class. Initialization of its object acquires the resource ("Resource Acquisition is Initialization"). Athe time we exit the block (block scope), the resource is freed again.
struct resource_holder{
resource_holder() {
r = allocate_resource();
}
~resource_holder() {
deallocate_resource(r);
}
resource* r;
}
void foo(){
resource_holder r;
if(...) {return;}
}
That is nice if you have got classes of their own which are not solely for the purpose of allocating/deallocating resources. Allocation would just be an additional concern to get their job done. But as soon as you just want to allocate/deallocate resources, the above becomes unhandy You have to write a wrapping class for every sort of resource you acquire. To ease that, smart pointers allow you automate that process.
-----------------------------------------------------------------------------------------------
A simple (and perhaps overused) example of RAII is a File class. Without RAII, the code might look something like this:
std::File file("/path/to/file");
// Do stuff with file
file.close();
In other words, we must make sure that we close the file once we've finished with it. This has two drawbacks - firstly, whenever we use File, we will have to call File::close() - if we forget to do this, we're holding onto the file longer than we need to. The second problem is what fi an exception is thrown before we close the file?
Java solves the second problem using a finally clause:
try {
File file = new File("/path/to/file");
// Do stuff with file
} finally {
file.close();
}
C++ solves both problems using RAII - that is, closing the file in the destructor of File. So long as the File objects is destroyed at the right time (which it should be anyway), closing the file is taken care of for us. So our code now looks something like:
class file
{
public:
file(const char* fileName):theFilePtr(std::fopen(fileName)
{
if(!theFilePtr)
throw std::runtime_error("Failed to open the file");
}
~file()
{
if(!std::fclose(theFilePtr))
// failed to flush the latest changes
// handle it
}
void write(const char* str)
{
if(EOF == std::fput(str, theFilePtr))
throw std::runtime_error("Failed to write to file!");
}
private:
std::File* theFilePtr;
}
With the preceding implementation of File, our code now looks something like:
File file("/path/to/file");
The reason this cannot be done in Java is that we have no guarantee over when the object will be destroyed, so cannot guarantee when a resurce such as file will freed.
Onto smart pointers - a lot of time, we just create objects on the stack. For instance ( and stealing an example from another answer):
void foo() {
std::string str;
// D o cool things to or using str
}
This works fine - but what if we want to return str? We could write this:
std::string foo() {
std::string str;
// Do cool things to or using str
return str;
}
so, whais wrong with that? Well, the return type is std::string - so it means we're returning by value. This means that we copy str and actually return the copy. This can be expensive, and we might want to avoid the cost of copying it. Therefore, we might come up with idea of returning by reference or by pointer.
std::string* foo() {
std::string str;
// Do cool things to or using str
return &str;
}
Unfortunately, this code doesn't work. We're returning a pointer to str - but str was created on the stack, so we be deleted once we exit foo(). Inother words, by the time the caller gets the pointer, it's useless (and arguably worse than useless since using it could cause all sorts of funky errors)
So, what's the solution then? We could create str on the heap using new - that way, when foo() is completed, str wo't be destroyed.
std::string* foo() {
std::string* str = new std::string();
// Do cool things to or using str
return str;
}
Of course, this solution isn't perfect either. the reasonis that we've
http://stackoverflow.com/questions/395123/raii-and-smart-pointers-in-c
No comments:
Post a Comment