Previous section   Next section

Imperfect C++ Practical Solutions for Real-Life Programming
By Matthew Wilson
Table of Contents
Chapter 20.  Shims


20.1. Embracing Change and Enhancing Flexibility

Unrelenting exposure to change is about the only constant thing in our industry. Whether writing application or systems software, it is inevitable that you will need to change existing code [Glas2003]. You want to know when you make a change that it can be done with as little pain as possible, with a consequent minimization of bugs. When writing open-source libraries, you need to make your libraries as flexible as is reasonably possible, because there's no chance that you'll anticipate all the weird uses to which people will put them. Let's familiarize ourselves with a couple of problems that change presents.

Because it supports a wide variety of expression of basic concepts, C++ can sometimes suffer from providing too many choices for representing the same concept. The most obvious example of this is that there are many different ways to represent strings. Not only are there different fundamental encodings (i.e., char or wchar_t), but also there is a large number of string classes, many having mutually incompatible syntax. The introduction of the standard library's basic_string template has helped somewhat, but it has also drawn critics ([Henn2002]), and does not answer all requirements. Being a general solution, there are some cases it cannot cater for, and some efficiencies it cannot take advantage of.

Consider the following extract defining a class to scope the current directory. (We saw some of the power of scoping classes in Chapter 6.)



template<typename C, . . .>


class current_directory_scope


{


public:


  explicit current_directory_scope(C const *dir);





  . . .


};



This interface is fine, as far as it goes. But if we want to use std::string in our client code, we are required to call its c_str() method in the client code, as in:



current_directory_scope    scope(s.c_str());



This ties in the client code to a particular string type (or rather a string model, one where the underlying C-style string is retrieved, or synthesized, via a c_str() method). That may not seem too great a problem in application code, although I maintain it is an unnecessary and undesirable restriction. But if the use of current_directory_scope was inside template code, this would limit its generality to those string classes that provide c_str() (and whose c_str() returns a C-style string[2]).

[2] Well, you never know!

Of course, if we had control or influence[3] over the author of the library, we might ask them to provide an overload for the constructor to take std::string const &. But again, this ties us to a specific string type, reducing the generality of our component needlessly.

[3] Library vendors are eminently receptive to sensible requests for changes; as well as being a great impetus for improvement, it's also a boost to your ego to know that someone's using your libraries.

A second source of consternation is in the use of pointers, and smart pointers, and their mixing, especially in conditional expressions. Most authors of smart pointers wisely eschew providing implicit conversion operators. But a consequence of this is that the writing of algorithms designed to work with raw pointers and smart pointers alike is complicated, or made impossible.



template <typename T>


void pass_to_API_if_non_null(T pt)


{


  if(NULL != pt)


  {


    ::SomeGlobalApiFunction(pt);


  }


}



This function works fine with raw pointers. It will also work with smart pointers that provide implicit conversion to their contained pointer type—we don't like those types, of course. But it will not work for smart pointers that wisely keep their pointer hidden under a bushel. For that, we would have to overload the method:



template <typename T>


void pass_to_API_if_non_null(std::auto_ptr<T> &pt)


{


  if(NULL != pt.get())


  {


    ::SomeGlobalApiFunction(pt.get());


  }


}



What if we need to provide similar "safe" functions for a number of different global/API functions and/or for other smart-pointer types? It's a lot of mundane, repetitive work.

There are myriad such examples of how syntax gets in the way of semantics. Dealing with them is very irksome: it can feel like the compiler is a niggardly nitpicking nanny, rather than your batman. When I change from one type to a semantically similar type, I want the compiler to sort things out for me. Of course it can't, unless we can give it a hand, so let's put some shims in the gaps and make things fit together properly.

The access shim concept (see section 20.6.1; [Wils2003c]) addresses the issue of rigidity in current_directory_scope and its use. Such classes can be given a high degree of generality and made able to deal with virtually any string type without introducing coupling or sacrificing efficiency. We'll see several examples of this throughout the remainder of the book.

By using attribute and logical shims (see sections 20.2 and 20.3; [Wils2003c]), raw vs. smart-pointer type problems are voided, and the generality and robustness (since we now have to change them much less frequently) of the algorithms is significantly increased.

The classic answer to everything in software engineering is to add a layer of indirection. This is the essence of what the shims concept is all about. But the layer is a very efficient layer, in most cases it adds no cost; where cost is involved, it is predominantly the case that is no more than would be incurred by equivalent conversion in client code. In any case, it is explicit, so the compiler is not bulking up (and slowing down) your code without being asked.


      Previous section   Next section