In my current position I spend a lot of time battling against a fairly poorly-written C++ code base. The code, while technically written in C++, is actually more of a C-like “splat” with a few classes thrown in. Since I began working on this project I’ve seen many cases where proper object-orientation would have made a drastic improvement to the quality of the code.
And it’s these cases which are the inspiration for this blog post.
Objects are not only good for encapsulation and making more readable/maintainable code. They can also be used to improve code safety. Rather than try and explain through words what I mean by ‘code safety’, let’s work through a couple of examples.
Example 1: Data Integrity in a Multithreaded Application
Let’s say that we’re working on a multithreaded application. We have a set of data that is constantly read and written across the different threads. We obviously don’t want to have different threads writing to the dataset at the same time, as this may cause all kinds of issues with the data’s integrity. We want to put a mechanism in place to ensure that the data’s integrity is never put at risk. We can do this simply by ‘locking’ the sections of code that perform the data writes via a synchronisation technique such as a critical section, a semaphore or a mutex. For the sake of this example, we’ll use a critical section. We’ll wrap it up in an object for easy use. Let’s pretend that we have a C++ critical section class that looks like this:
1 2 3 4 5 6 7 8 9 10 11 | |
To use the critical section object, all we need to do is this:
1 2 3 4 5 6 7 8 9 | |
Easy enough isn’t it! We’ll use this code in our data writer object, so that we can make sure that all of our threads are playing nicely while writing the data. During the course of our data writing, we might want to do some other processing that is not related to writing data. Even though this task isn’t related to writing the data out, we still retain the lock so that we can finish off the job after without having to unlock and relock. So we may have some code that looks like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
Not really rocket science is it? The above code makes sure that all the code between Lock() and Unlock() is only run when no other threads are requesting a lock (or own a lock) on the critical section.
Unfortunately we have a problem. In a non-perfect world (like the one we live in) there’s always a chance that somewhere between the call to Lock() and the call to Unlock() an error might occur. An exception may be thrown. The code might just return;. If that’s the case, the critical section will not be unlocked because the Unlock() function won’t be called before the function finishes.
The result? A critical section that’s permanently locked, and all future Lock() requests will either hang or fail. This is not ideal. If we were using system-wide mutexes we’d do some serious damage! Cleary this isn’t acceptable.
Most procedural programmers will adjust the code in the following manner:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | |
Some of you might be laughing at the above code. And rightly so! But don’t think that it’s uncommon because it’s not. As you can imagine, the code gets bigger, nastier and tougher to maintain. As the function increases in size, code duplication increases and the chance of making mistakes increases. What if a new maintenance coder decides to early-out without unlocking? You’re still stuck with the same problem.
Further use of object-orientation is what’s required to solve this problem. Let’s use and abuse a couple of the core features of objects: construction and destruction:
- When an object is instantiated, it’s constructor is called.
- When an object is destroyed, it’s destructor is called
- When they go out of scope.
- When they are deleted.
- … program execution reaches the end of the function.
- … a return statement is reached.
- … a exception is thrown that is not caught.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | |
If we use this object instead of the verbose and repetitive method shown above, our code would look like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | |
This code is substantially easier to maintain. As soon as csl goes out of scope, the critical section will be unlocked automatically via the destructor. This will happen on early-outs and when exceptions are thrown. Job done!
Example 2: Flag/Value Toggling
Developers have been known to write code which relies on object state to function differently. While this might not be considered ‘best practice’, it’s fairly common out in the wild. There are times where a developer may want to temporarily toggle a flag, or change the value of a variable, and change it back before the rest of the code is run. Here’s an example (it’s not real-life, but it should give you the idea):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | |
Don’t laugh, we’ve all been there! :)
It should be obvious that the code above suffers from the same shortcomings as the previous example. It’s not resiliant against early-outs or exceptions. A similar type of construct/destruct mechanism can be used to reset the original value of a variable in a much safer way. An example class might look like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | |
The object above is templated so that it can be used with a variety of types (eg. int, double, float). Using an object such as this, the example program code would change to the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | |
Again, the code is smaller and a lot more bulletproof. The value of m_Age will be restored no matter what happens inside the function.
Conclusion and Final Thoughts
The above samples should show you how easy it is to use objects to help you write more defensive code. Though some people (usually the procedural programmers ;) ) would say that this kind of code is a hack, it’s my view that they’re wrong. The code is clear, easy to maintain, and very safe.
Disclaimer: The code above hasn’t been compiled, so it may contain errors. The point of the post is to show you the idea behind using objects for safety. Ideal implementations are beyond the scope of the article.
Thoughts and feedback are always welcome.