Living Without Using
I’ve been working on an internal project using C++/CLI which, honestly, feels different from Managed C++, which in turn feels different from C++. C++/CLI feels the softest of the set, if that makes any sense.
At any rate, C++ doesn’t have the using() { } syntax which I’ve grown to love in C#. That’s OK, there are plenty of ways to work around this. Let’s say that you have the following code as a baseline:
void SomeClass::WriteToFile(String ^path, array<byte> ^data)
{
FileStream ^stm = gcnew FileStream(path, FileMode::Create);
stm->Write(data, 0, data->Length);
stm->Dispose();
}
This is straight forward – open a file for creation, write to it and close it. The problem is that if Write fails, it will throw and the stream will never get cleaned up. We can work around this by wrapping this in a try/__finally:
void SomeClass::WriteToFile(String ^path, array<byte> ^data)
{
FileStream ^stm = gcnew FileStream(path, FileMode::Create);
try {
stm->Write(data, 0, data->Length);
}
__finally {
delete stm; // calls Dispose
}
}
This handles our problem and lets someone upstream take care of the exception. The existing code is only slightly uglier than it was before. The problem is that once you start using multiple disposable objects within this method, the code will get uglier and uglier and you’ll find yourself sacrificing logical code flow just to deal with disposing resources.
There is a better idiomatic solution: RAII. Classes that are constructed on the stack must be destructed when they go out of scope. This includes when code throws an exception. Therefore, we can make the following utility class:
private ref class AutoDispose {
public:
AutoDispose(IDisposable ^p) : _p(p) { }
~AutoDispose() { delete _p; }
private:
IDisposable ^_p;
};
This is a class that keeps a reference to an object that implements IDisposable and when AutoDispose is destructed, it disposes the class. It’s OK if p is nullptr because operator delete is supposed to ignore null objects. To give you a sense of how this works, we can rewrite our code like this:
void SomeClass::WriteToFile(String ^path, array<byte> ^data)
{
FileStream ^stm = gcnew FileStream(path, FileMode::Create);
AutoDispose stmDispose(stm);
stm->Write(data, 0, data->Length);
}
at this point, I’m done (from my point of view). This gives us disposal of the object that is immune to throws and keeps our code logic clean. You can go one step further and encapsulate the object more by using generics:
generic <class T> where T:IDisposable ref class AutoDisposeRef {
public:
AutoDisposeRef(T p) : _p(p) { }
~AutoDisposeRef() { delete _p; }
T operator ->() { return _p; }
private:
T _p;
};
Now I have a class that will pretend to be the class you’re initializing it to. Again, rewriting our example to use the new class:
void SomeClass::WriteToFile(String ^path, array<byte> ^data)
{
AutoDisposeRef<FileStream ^> stm(gcnew FileStream(path, FileMode::Create);
stm->Write(data, 0, data->Length);
}
Stm has become a lightweight adapter for FileStream by overloading operator –>. stm->Write isn’t really a dereference. Instead, it passes on Write to the encapsulated FileStream. I don’t like this approach myself – to me it’s cognitive dissonance to see a stack object using –> – it makes me think that the stack object is a pointer and that doesn’t feel right. Also note that this isn’t complete. I can’t pass stm into another method, so clearly we need a way of getting the object out, perhaps a cast operator or operator address of or a method like T get() { return _p; }. Nor does this code address copy contructors. If you want to go further, you might consider porting auto_ptr<T> from STL to C++/CLI.