| To better understand the importance of RAII, you might consider what some other languages offer to solve similar problems. Consider a C example: int f()
{
int ret = -1;
handle * a = acquire_handle(0);
if (a == NULL) goto fail1;
handle * b = acquire_handle(1);
if (b == NULL) goto fail2;
handle * c = acquire_handle(2);
if (c == NULL) goto fail3;
// use a, b, c
ret = 0;
release_handle(c);
fail3:
release_handle(b);
fail2:
release_handle(a);
fail1:
return ret;
}
Consider a C# example: void f()
{
using (handle a = new handle(0))
using (handle b = new handle(1))
using (handle c = new handle(2))
{
// use a, b, c
}
}
Now a C++ example using RAII: void f()
{
handle a{0};
handle b{1};
handle c{2};
// use a, b, c
}
These examples are mostly equivalent (Although the C#/C++ assume exceptions instead of error codes).The C#/C++ examples are far more structured and less error prone than the C example. The advantage of C++'s RAII over C#'s using statement is that cleanup is tied to the object rather than a statement. This means that RAII is both composable and non-leaky as an abstraction. You cannot forget to destruct an object in C++, and you don't have to care that its destructor frees resources. When you have an IDisposable member in C# you must manually mark your class as IDisposable and then implement the Dispose method yourself. Clients must also be aware that your class is IDisposable in order to use it correctly. |