Computing stuff tied to the physical world

A bag of C++ tricks

The Romvars implementation uses a few advanced C++ features, including:

  • using a C++ template to isolate the hardware-dependent code
  • using a C++ nested class to provide virtual array-like access to entries

The template mechanism makes it possible to define both a 64-byte “Flash64” and a 1024-byte “Flash1k” implementation. Unlike the normal OO subclassing mechanism, all of the differences are handled by gcc at compile time, leading to more efficient and more compact code – often substantially so. Templates are a powerful tool for resource-constrained embedded environments, but some care is needed to avoid making things overly complex.

The basic idea here is that the RomVars is not a subclass of some Flash class (or more likely: vice-versa), but that the Flash class is “integrated” (or perhaps “injected”) into RomVars at compile time through a template:

    template < typename FLASH >
    class RomVars {
      ...
      FLASH flash;
    };

So flash is a member of RomVars, of type “FLASH” (a place-holder, not the real type).

One immediate advantage is that the constants in the Flash class become available as constants in the RomVars class. This is why the following lines inside RomVars work:

    enum { NumVars = FLASH::PageSize / sizeof (Tuple) };
    uint8_t map [NumVars];

In other words, a RomVars instance (object) includes a map member which is an array of fixed size, but that size is determined by this definition inside the Flash class:

    enum { PageSize = 64 };

Note the use of the all-capitals “FLASH” as template parameter, which gets filled in when a RomVars instance is defined in the application:

    RomVars<Flash64,0x0F80> rom;

The reason for that second numeric “0x0F80” argument is that the real RomVars class was in fact defined slightly differently – with two template arguments:

    template < typename FLASH, int BASE >
    class RomVars {
      ..
    };

Not only does the RomVars class have access to a class telling it how to perform reads and writes to flash memory, it also has a constant value telling it the location in flash memory.

The power from all this comes from being able to use a different flash implementation, at a different location, with all the differences compiled-in (and optimised!) at compile time.

But there is much more to it – this decoupling of the permanent variable implementation code from the actual load/save code could also be used to store the variables in a separate chip, connected via an I2C or SPI bus, or even to store the actual “permanent” data in a remote node, accessed via some wired or wireless protocol. In other words: the RomVars class implements a change mechanism, using two pages with somethingsomewhere!

All we need is a suitably defined “flash-like” class, with the proper members and constants. The ones currently defined can be found on GitHub. More can be defined later, by anyone.

Note that all this code resides in header files, as is common with templates. That’s because templates are very much a compile-time mechanism, so most of the app code needs to have access to the entire implementation – for the compiler to apply the proper substitutions.

So in a way, C++ templates behave like an advanced (and type-safe!) macro pre-processor.

On to the second advanced C++ trick in this RomVars class.

As mentioned earlier, a RomVars object acts very much as an array, allowing uses such as:

    rom[7] = 0x1234;
    if (rom[8] == 0x5678) ...
    rom[9] = rom[9] + 1;

This relies on several fancy C++ features: overloading of the “[]” array operator, the “cast to uint16_t” operator, and the assignment operator. It also depends on C++ “references” (which is a way to work with pointers without having to use “*” to de-reference them).

There’s a lot to go through to cover all the intricacies of this trick, but it boils down to this:

  • writing rom[7] triggers the C++ operator[] definition in RomVars
  • this returns a special kind of object, of type RomVars::Ref
  • that nested class is defined inside RomVars (and not available elsewhere)
  • the purpose of this object is to remember the RomVars object and the array index
  • when used on the right-hand side of an equals sign, as part of an integer expression, the operator uint16_t implementation is triggered, which calls back into the RomVars object to fetch the appropriate index (using the RomVars::at() method)
  • when used on the left-hand side of an equals sign, we call the RomVars::set() method to perform a storage operation, again at the remembered array index

Note that every use of rom[...] returns a temporary instance of type RomVars::Ref (on the stack), but this gets used and cleaned up automatically after use. The gcc compiler is very good at optimising the resulting code, to the point that the actual generated code doesn’t even have this temporary object anymore. It’s all conceptual smoke and mirrors…

The details and notation in C++ to accomplish all this are fairly nasty – perhaps mostly due to the fact that C++ was defined long after C was invented, and did so without breaking all the other C syntax conventions. But the power does lead to a RomVars class which walks, swims, and quacks like a simple array of permanent variable values. No “get” and “set” methods (or fetch/store, at/put, insert/delete, etc) – it all ends up looking like an array.

Hidden inside library definitions, C++ templates and operators can lead to simple code.

At the end of the day, to use this “permanent variable” mechanism, all you need to do is:

  • define a RomVars object, for example: RomVars<Flash1k,0x3800> rom;
  • initialise it before use, by inserting this call: rom.init();
  • use the entries as variables, i.e.: ... = rom[3]; or rom[4] = ...;

And if you want to find out more and investigate / learn all the details: they are all there, out in the open as wide open source, in the flash.h and romvars.h source files on GitHub.

[Back to article index]