![]() | |
![]() ![]() |
![]() | Imperfect C++ Practical Solutions for Real-Life Programming By Matthew Wilson |
Table of Contents | |
Chapter 20. Shims |
20.4. Control Shims
Control shims are less widely used than attribute and logical shims, because generalization of operations is less ubiquitous than the accessing of attributes or states. (We saw a pair of control shims in section 6.2 when we talked about state scoping.) Control shims are not only used for designing flexible policy-based general components; they are also exceedingly useful tools for assisting in maintenance, especially in porting code. Consider having some nontrivial code that makes extensive use of instances of one or more container types. Say it's the source for a Linux PC game. You then have a requirement to port it to an embedded game platform. As such you want to change the container type(s) used, and make use of a highly optimized in-house container library, specifically written for the new platform. Unfortunately, the container library was written a long time before the Standard Template Library (STL) Sequence and Associative Container concepts [Aust1999, Muss2001] became a widely accepted form, and so does not conform. What do you do? Well, you could insist that the container library be rewritten to conform to the STL. Regrettably, the original authors long since left the company, and although they left a very comprehensive test suite[5] that gives you confidence in the quality of the container library, no one in the company has much expertise in the internals of this library. The changes would be nontrivial, deadlines are tight, and you cannot justify the risk of wholesale changes.
Alternatively, you could populate the source with #ifdefs, specializing client-code use of the container libraries (remember we're now conceptually dealing with two) via the preprocessor. This is a workable solution in the short term, but as an experienced cross-platform developer, you know this is just setting yourself up for more grief down the line. The classic advice in these scenarios is to abstract all platform-specific code into separate implementation files, and use link-time selection to keep the messiness to a minimum. While this is great for truly platform-specific areas—file systems, memory mapping, synchronization objects—in some cases it is hopelessly naïve. In this case it is a bad idea because:
Given these choices, most of us would go for the second option, and build in all the #ifdefs under the sun. The product would ship on time, but would become a maintainer's nightmare.[6] As soon as the developer who handled the first port to the new platform left the company, moved to another project, or perhaps even just got a full night's sleep, it would be impossible to enhance without adding bugs to one or the other (or both) implementations. Never mind adding another target platform!
So what's the answer? Shims, of course: attribute shims to get hold of elements; logical shims to ascertain container and element states; control shims to change the contents of the containers. make_empty() to clear the container of its elements; insert_element() to insert an element; remove_element() to remove an element; get_nth_element() to access an element by index.[7]
You can have a single code base, with perhaps only a single preprocessor conditional to typedef the container types, and you write and use shims that are highly efficient and are inlined with no cost by the compiler. The code barely has to change, and does so in ways that are either meaning positive or neutral at worst—make_empty(cont); is a lot more obvious to a non-STL developer than cont.erase(cont.begin(), cont.end());—such that you can test with all existing test cases on the original platform, knowing that you have effected no semantic changes whatsoever. When it comes time to add new container types for your next platform, you can do so without changing a character of the valuable and fragile application code. |
![]() | |
![]() ![]() |