First of all let me say that I'm creating software for microcontrollers, so RAM usage matters and it makes sense to put large blocks of const data to non-volatile (flash) memory.
What I'm trying to achieve is to find a nice way to create a "split" object in C++. As an example let's assume that there's one byte of data (read/write) and a multibyte "receipt" for accessing it. Let's say that the "receipt" is a long string that is a filename, and the media that it points to is slow, so it makes sense to buffer the single byte in memory, instead of actually reading it on each request.
class Data
{
uint8_t byte;
bool valid;
const char filename[128];
uint8_t read()
{
if (!valid)
performReallySlowRead(filename, &byte);
valid = true;
return byte;
};
void write(uint8_t new_value)
{
byte = new_value;
performRealWriteToMedia(filename, byte);
valid = true;
};
}
The obvious problem with this approach is that whole 130-bytes end up in RAM, while only two of them need to be changed. So I've come up with an idea of split-object:
class DataNonConst
{
uint8_t byte;
bool valid;
}
class DataConst
{
const char filename[128];
DataNonConst * const data;
}
static DataNonConst storage;
const DataConst holder("some_long_name_here", &storage);
Now the only problem is that if I would like to have a few hundred of such split-objects the process of creating them (so creating TWO objects and linking second to first) gets pretty boring and problematic...
So the question is - is there some nice way to make it easier to use, preferably a clever-C++-trick or maybe some template magic? That is - how to create TWO objects, linked together, with a single statement, preferably one object is hidden? I don't think macro-solution is possible here, as there's no easy way to automate creation of the name for storage object... The objects need to be of same type, as I need to embed pointers to such objects in other places (one function deals with writing them, other only cares about reading)... All the solutions that I've thought of either require use of virtual interface to templates (so you make the object bigger by vtable pointer AND probably get a bonus template-bloat) or result in huge template bloat...
EDIT:
Actually part of the whole problem can be reduced to a simplier question - is there a way to "bind" an anonymous variable to a member field in C++? Sth like:
const ConstData holder("...", NonConstData()); // this object HAS TO be ROMable
In the above "wishful thinking" holder is const object in ROM and it has a pointer/reference/whatever to an anonymous object NonConstData created "somewhere" in RAM. Or:
std:pair<const ConstData &, NonConstData &> holder(ConstData(), NonConstData());
Anything that would allow me NOT to manually create both objects and bind one to another.
You can replace a pointer with an integer, and create a single static array for all your DataNonConst
objects, like this:
class DataNonConst {
uint8_t byte;
bool valid;
};
static DataNonConst storages[MAX_DATA];
class DataConst {
const char filename[128];
const int dataIndex;
DataNonConst *data() {
return &storages[dataIndex];
}
};
const DataConst holderOne("first_long_name_here", 0);
const DataConst holderTwo("second_long_name_here", 1);
const DataConst holderThree("third_long_name_here", 2);
This approach is inspired by the Flyweight Pattern, although obviously it's used for a different purpose here.
An obvious downside to this is that you need to count your entries manually to avoid duplication. However, there is only one storages
object, so there's nothing else to create.
As far as I see, the only issue here is that your "Handle" object has a char[]
member. I bet if you replace that with a const char*
, that solves your issues, since string literals are probably in the non-volatile memory anyway.
class Data
{
const char* filename;
uint8_t byte;
bool valid;
Data(const char* filename_) : filename(filename_), valid(false) {}
uint8_t read()
{
if (!valid)
performReallySlowRead(filename, &byte);
valid = true;
return byte;
};
void write(uint8_t new_value)
{
byte = new_value;
performRealWriteToMedia(filename, byte);
valid = true;
};
};
Data holder("some_long_name_here"); //name is in the non-volatile memory anyway
char buffer[128] = "\var\bin\omg";
Data holder2(buffer); //runtime names work fine, but have to manage the storage yourself.
Or, if you want to be crafty, you can make this a template and save even that amount of memory (at the expense of being slightly more confusing. This has the added benefit of being able to easily add static
members, which will also be in non-volatile memory, but not shared per filename. It's not obvious, but this can be used with files whos names are not known at runtime, but I don't recommend doing that.
template<const char* filename>
class Data
{
uint8_t byte;
bool valid;
uint8_t read()
{
if (!valid)
performReallySlowRead(filename, &byte);
valid = true;
return byte;
};
void write(uint8_t new_value)
{
byte = new_value;
performRealWriteToMedia(filename, byte);
valid = true;
};
};
Data<"some_long_name_here"> holder;
extern char buffer[128] = "\var\bin\omg";
A<buffer> holder2; //this always refers to the filename currently in buffer
However, if you really want a split object, your what you have as the split suggestion is really the best answer I can think of.
With some restrictions, you can leverage templates to create a static instance of DataNonConst that is referenced by each ROMable DataConst objects.
Restrictions:
The code uses the __LINE__
macro and thus there must only be one DataConst declaration per line. Alternatively, if the __COUNT__
macro is available, it can be used in place of __LINE__
to allow
multiple declarations per line.
The DataNonConst
objects are always zero-initialized.
Code:
struct DataNonConst
{
uint8_t byte;
bool valid;
};
struct DataConst
{
const char filename[128];
DataNonConst * const data;
};
namespace {
template <long n>
struct DataNonConstHolder
{
static DataNonConst data;
};
template <long n> DataNonConst DataNonConstHolder<n>::data;
}
// If __COUNT__ macro is supported, use it instead of __LINE__
#define Initializer(s) { s, &DataNonConstHolder<__LINE__>::data }
const DataConst dc1 = Initializer("filename1");
const DataConst dc2 = Initializer("filename2");
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With