Bundled vs. unbundled monostate classes
In my column last May, I presented some common alternatives for representing and manipulating memory-mapped devices in C. I recommended using a structure to represent each device's collection of registers as a distinct type.1 In June, I explained why C++ classes are even better than C structures for representing memory-mapped devices.2
In Standard C and C++, you can't declare a memory-mapped object at a specified absolute address, but you can initialize a pointer with the value of that address. Then you can access that object by dereferencing the pointer. That's what I did in those articles last spring.
Several readers posted comments on Embedded.com about both my May and June columns. A few of those comments alleged that using a pointer to access a C++ class object representing a memory-mapped device incurs a performance penalty by somehow adding unnecessary indirect addressing, yet no one complained that using a pointer to access a C structure incurs a similar performance penalty. Nonetheless, I decided that it was worth investigating whether the unnecessary indirection is a problem in either language.
In my August column, I described available C and C++ features (both standard and non-standard) for placing objects into memory-mapped locations.3 In September, I presented alternative data representations for memory-mapped device registers in both C and C++ that eliminate the need to use pointers to access memory-mapped devices.4
As I explained at the end of my September column, I've run some timing tests on memory-mapped data accesses using a few different C and C++ compilers. I had expected to publish those results this month. However, as I reviewed the data, I noticed a few unexpected blips. Upon closer inspection, I discovered some inconsistencies in how I implemented a few test cases. This led me to refine some of the techniques I presented in September.
This month, I'll revisit the memory-mapped data representations that don't use pointers and explain how slightly different packaging might, on some processors, lead to better or worse performance.
Monostate vs. polystate
Back in June, I presented a C++ class called timer_registers that encapsulates the entire collection of registers for a hypothetical timer as a single abstract type. A simplified version of the class definition appears in Listing 1.
Click on image to enlarge.
All of the member functions in the timer_registers class are "ordinary"—neither static nor virtual. Every ordinary C++ class member function is conceptually equivalent to a C (non-member) function with an additional parameter. That additional parameter is a pointer to the object (in this case, the timer) upon which the member function acts. Every ordinary member function call passes a value for the pointer. When readers expressed concern about C++ classes incurring unnecessary run-time costs, they were talking about the cost of passing and using this pointer.
In September, I showed how you can avoid passing and dereferencing those pointers by using static members in the class implementation. In particular, you can rewrite the timer_registers class so that every data member and every member function is declared static , as in Listing 2.
Click on image to enlarge.
This implementation is appropriate only when you're using a single timer.
This implementation is actually an application of a software design pattern called the monostate pattern. This is not one of the original "Gang of Four" patterns, but it's become fairly well known.5 My monostate implementation is subtly different from traditional monostate implementations.6,7 A traditional monostate class uses ordinary (non-static) member functions, so that it behaves more like an ordinary class. Unfortunately, using ordinary member functions preserves the compiler-generated pointer parameters. My implementation uses static member functions to eliminate those compiler-generated parameters.
As far as I know, there's no specific classification for a class, such as the timer_registers class in Listing 1, that's not a monostate. When I need to identify a class as having a non-monostate implementation, I call it a polystate class. (The prefix poly- means "many", in contrast to mono- which means "one".)
A program can create multiple instances of a polystate class. Each such instance has its own state (its own set of data members). When your hardware has more than one of a given device, say four timers or two serial ports, you should use a polystate implementation.
A program can also "create" multiple objects of a given monostate class; however, all those monostate objects share the same statically-allocated state. You might reasonably consider attempting to create multiple instances of a monostate to be a program error. There are techniques you can use to prevent this, but they're a topic for another day.