I'm working on some embedded software where there is some static information about "products". Since the information for a certain product never changes during execution I would like to initialize these data structures at compile time to save some space on the stack/heap.
I made a Product class for the data, intending to make a huge array of all the products in the system and then do lookups in this structure, but I haven't figured out quite how to get it working. The arrays are giving me loads of trouble. Some psuedo code:
class Product {
int m_price;
int m_availability[]; // invalid, need to set a size
... etc
// Constructor grabbing values for all members
Product(int p, int a[], ...);
}
static const Product products[] =
{
Product(99, {52,30,63, 49}, ...), // invalid syntax
...
}
Is there a way to making something like this work? The only thing I can think of would be to organize by attribute and skip the whole Product object. I feel that would make the whole thing harder to understand and maintain though.
Does anyone have any suggestions on how I might best organize this kind of data?
Thank you.
An old school C style static array of structs sounds like a perfect match to your requirements. Initializes at compile time, zero runtime overhead, no use of stack or heap. It's not a co-incidence that C is still a major player in the embedded world.
So (one recipe - plenty of scope to change the details of this);
// in .h file
class Product {
public: // putting this first means the class is really a struct
int m_price;
int m_availability[4];
//.... (more)
};
extern const Product product_array[];
extern const int product_array_nbr;
// in .cpp file
const Product product_array[] =
{
{
23,
{56,1,2,4},
//....(more)
},
{
24,
{65,1,2,4},
//....(more)
},
//....(more)
};
const int product_array_nbr = sizeof(product_array)/sizeof(product_array[0]);
A couple of years ago when I was working in embedded we needed to explicitly control the memory allocation of our structures.
Imagine this type of struct :
.h file
template<class T,uint16 u16Entries>
class CMemoryStruct
{
public:
/**
*Default c'tor needed for every template
*/
CMemoryStruct(){};
/**
*Default d'tor
*/
~CMemoryStruct(){};
/**
*Array which hold u16Entries of T objects. It is defined by the two template parameters, T can be of any type
*/
static T aoMemBlock[u16Entries];
/**
*Starting address of the above specified array used for fast freeing of allocated memory
*/
static const void* pvStartAddress;
/**
*Ending address of the above specified array used for fast freeing of allocated memory
*/
static const void* pvEndAddress;
/**
*Size of one T object in bytes used for determining the array to which the necessary method will be invoked
*/
static const size_t sizeOfEntry;
/**
*Bitset of u16Entries which has the same size as the Array of the class and it is used to specify whether
*a particular entry of the templated array is occupied or not
*/
static std::bitset<u16Entries> oVacancy;
};
/**
*Define an array of Type[u16Entries]
*/
template<class Type,uint16 u16Entries> Type CMemoryStruct<Type,u16Entries>::aoMemBlock[u16Entries];
/**
*Define a const variable of a template class
*/
template<class Type,uint16 u16Entries> const void* CMemoryStruct<Type,u16Entries>::pvStartAddress=&CMemoryStruct<Type,u16Entries>::aoMemBlock[0];
template<class Type,uint16 u16Entries> const void* CMemoryStruct<Type,u16Entries>::pvEndAddress=&CMemoryStruct<Type,u16Entries>::aoMemBlock[u16Entries-1];
template<class Type,uint16 u16Entries> const size_t CMemoryStruct<Type,u16Entries>::sizeOfEntry=sizeof(Type);
/**
*Define a bitset inside a template class...
*/
template<class Type,uint16 u16Entries> std::bitset<u16Entries> CMemoryStruct<Type,u16Entries>::oVacancy;
Depending on your compiler and environment you could manipulate the area of where the static allocation take place. In our case we moved this to the ROM which was plenty. Also note that depending on your compiler i.e. Greenhills compilers, you may need to use the export keyword and define your static members to the .cpp file.
You can use the start and end pointers to navigate through the data. If your compiler supports full STL you may want to use std::vectors with custom allocators and overloaded new operators which would save your memory to somewhere else than the stack. In our case the new operators were overloaded in such a way that all the memory allocation was done on predefined memory structures.
Hope I gave you an idea.
In C++98/03, you cannot initialize arrays in a constructor initializer.
In C++11, this has been fixed with uniform initialization:
class Product
{
int m_availability[4];
public:
Product() : m_availability{52,30,63, 49} { }
};
If you need the data to be provided in the constructor, use a vector instead:
class Product
{
const std::vector<int> m_availability;
public:
Product(std::initializer_list<int> il) : m_availability(il) { }
};
Usage:
extern const Product p1({1,2,3});
Memory for the static variables is still reserved when the code is actually executing -- you won't be saving space on the stack. You might want to consider use of vectors instead of arrays -- they're easier to pass and process.
Related
I need to reduce the memory used by my native Windows C++ application, without compromising its performances.
My main data structure is composed by several thousands of instances, dynamically allocated, of the following Line class:
struct Properties
{
// sizeof(Properties) == 28
};
// Version 1
class Line
{
virtual void parse(xml_node* node, const Data& data)
{
parse_internal(node, data);
create();
}
virtual void parse_internal(xml_node*, const Data&);
void create();
Properties p;
};
But since I notice that I could get rid of the class member p, because I only need it within the parse method, I changed the Line implementation:
// Version 2
class Line
{
virtual void parse(xml_node* node, const Data& data)
{
Properties p;
parse_internal(node, data, &p);
create(&p);
}
virtual void parse_internal(xml_node*, const Data&, Properties*);
void create(Properties*);
};
This reduced the memory allocated of several megabytes, but it increased the elapsed time by more than 50 milliseconds.
I wonder how is this possible considering that the application has been compiled for release version with speed optimization fully on. Is it due to the argument passing? Is it due to the stack allocation of my struct Properties?
Update:
The method Line::parse is called just once for each instance. The data structure is composed by a std::vector of Lines. Multiple threads manage a different subset of this vector.
You write that parse_internal is recursive. That means it gets 3 arguments in the changed variant, instead of 2 in the original - and is called recursively a few times.
You also have to access members using pointer syntax instead of element de-referencing (and possibly verify that the Properties pointer is non-null). To eliminate the pointer issues you can use a reference argument to parse_internal.
Is there a reason to have parse_internal as a virtual member function, or could you change it to be static (in the modified variant)?
Context:
I am writing a specific communication protocol to be used between TLM models (HW blocks described with SystemC and thus C++).
TLM notion is not important, just note that this communication is mimicked by allocating objects, the generic payloads (gps), that are passed between these C++ models of HW blocks.
Aim:
Together with the protocol, I want to provide a memory manager that should be able to efficiently handle the gps; this is quite important since in one simulation lots of gps are constructed, used and destroyed and this can slow down things a lot.
My goal is also to create something simple that could be used by others without efforts.
Issues:
The first issue I had was in creating a single shared pool for all the blocks communicating with that protocol. I thought about creating a static member in the mm class, but then I realized that:
Static members require a definition in the cpp. This makes the mm class less intuitive to use (with different people using this, some will forget to do so) and I would prefer to avoid that.
Depending on where (and in which?) in the cpp file the static variable definition is done, the pool might not have wet the parameters needed to be initialized (i.e., the number of mm instances created).
The second issue is similar to the first one. I want to count the number of instances and thus instead of a pool I need to create a shared counter to be used then by the pool to initialize itself. Again, I wanted to avoid static variable definitions in a cpp file and to guarantee the order of initialization.
I have considered mainly:
static members (discarded for the reasons above)
Singletons (discarded because I don't need to create a whole class for the pool to make it visible by others and single-instanced)
static methods (the approaches I finally picked and that is not far from a complete Singleton)
This is the code I produced (only relevant part included):
/**
* Helper class to count another class' number of instances.
*/
class counter {
public:
// Constructor
counter() : count(0) {}
//Destructor
virtual ~counter() {}
private:
unsigned int count;
public:
unsigned int get_count() {return count;}
void incr_count() {count++;}
void decr_count() {count--;}
};
template <unsigned int MAX = 1>
class mm: public tlm::tlm_mm_interface {
//////////////////////////////TYPEDEFS AND ENUMS/////////////////////////////
public:
typedef tlm::tlm_generic_payload gp_t;
///////////////////////////CLASS (CON/DE)STRUCTOR////////////////////////////
public:
// Constructor
mm() {inst_count().incr_count();}
// Copy constructor
mm(const mm&) {inst_count().incr_count();}
// Destructor
virtual ~mm() {} // no need to decrease instance count in our case
////////////////////////////////CLASS METHODS////////////////////////////////
public:
// Counter for number of isntances.
static counter& inst_count() {
static counter cnt;
return cnt;
}
/* This pattern makes sure that:
-- 1. The pool is created only when the first alloc appears
-- 2. All instances of mm have been already created (known instance sequence)
-- 3. Only one pool exists */
static boost::object_pool<gp_t>& get_pool() {
static boost::object_pool<gp_t> p(
mm<MAX>::inst_count().get_count() * MAX / 2, // creation size
mm<MAX>::inst_count().get_count() * MAX // max size used
);
return p;
}
// Allocate
virtual gp_t* allocate() {
//...
return gp;
}
// Free the generic payload and data_ptr
virtual void free(gp_t* gp) {
//...
get_pool().destroy(gp);
}
}
Now, the initiator block class header should have a member:
mm m_mm;
And the initiator block class cpp should use this like:
tlm_generic_payload* gp;
gp = m_mm.allocate();
//...
m_mm.free(gp); // In truth this is called by gp->release()...
// ...not important here
Having an electronic HW background, I am mainly trying to improve coding style, learn new approaches and optimize speed/memory allocation.
Is there a better way to achieve this? In particular considering my doubts:
It seems to me a not optimal workaround to encapsulate the counter in a class, put it locally (but static) in a static method and then do the same for the pool.
even though SystemC "simulation kernel" is single-threaded, I need to consider a multithread case...I am not sure that the relationship between those two static methods is safe even thou independently they should be safe...with C++03 g++ adds code to guarantee it and with C++11:
ยง6.7 [stmt.dcl] p4 If control enters the declaration concurrently while the variable is being initialized, the concurrent execution shall wait for completion of the initialization.
Thanks in advance.
I am trying to unit test a C++ application that I am building and I'm having an issue initializing the array when used in a class. I've tried alot of different methods of loading this information, the only ones that work are inefficient / not suitable.
Here is the hex array that I have (randomised the parts)
0x24,0x54,0x3b,0x72,0x8b,0x03,0x24,0x29,0x23,0x43,0x66,0x22,0x53,0x41,0x11,0x62,0x10
And header file for my unit test:
class MessageParsingTest : public CPPUNIT_NS::TestFixture {
CPPUNIT_TEST_SUITE(MessageParsingTest);
CPPUNIT_TEST(testIdentifyFirstMessageType);
CPPUNIT_TEST_SUITE_END();
public:
MessageParsingTest();
virtual ~MessageParsingTest();
void setUp();
void tearDown();
private:
void testIdentifyFirstMessageType();
void testIdentifySecondMessageType();
// data members for the hex array
unsigned char firstMessage[1500];
};
Then in my test case setUp function;
void MessageParsingTest::setUp() {
firstMessage = {0x24,0x54,0x3b,0x72,0x8b,0x03,0x24,0x29,0x23,0x43,0x66,0x22,0x53,0x41,0x11,0x62,0x10};
}
That it my latest failed attempt, it says its not valid during compilcation, as I expected, but at this point I was trying anything.
I've also tried things like (all in setUp function)
firstMessage << "\0x24\0x54\0x3b\0x72\0x8b\0x03\0x24\0x29\0x23\0x43\0x66\0x22\0x53\0x41\0x11\0x62\0x10";
firstMessage[1500] = "\0x24\0x54\0x3b\0x72\0x8b\0x03\0x24\0x29\0x23\0x43\0x66\0x22\0x53\0x41\0x11\0x62\0x10";
and a few other crazy ways, Does anyone know the proper way to load this data? the only way I've had it working so far is with either no data member declaration and straight up defining it and initializing in one line (but then I cant access in the test cases) or doing it one by one like firstMessage[0] = 0x24; etc.
I understand that there will be a simple, proper way of doing this and considering what the application actually does, this part should be the easiest.
You have few options:
Initialize arrays in constructor MesssageParsingTest using syntax : firstMessage{0x24,0x54,0x3b,0x72,0x8b,0x03,0x24,0x29,0x23,0x43,0x66,0x22,0x53,0x41,0x11,0x62,0x10}
in initializer list.
Create static const array containing your message, and either copy it to member variable using memcpy, or use static member and get rid of firstMessage member variable.
Declare const static member in .h inside class definition:
static const unsigned char kFirstMessage[];
and define + initialize it in .ccp
const unsigned char MessageParsingTest::kFirstMessage[] = "\0x24\0x54\0x3b\0x72\0x8b\0x03\0x24\0x29\0x23\0x43\0x66\0x22\0x53\0x41\0x11\0x62\0x10";
I would prefer static const member if you do not intend to modify this array later, since it makes the intention cleaner.
Here is one way to do it.
void MessageParsingTest::setUp()
{
unsigned char x[] = {0x24,0x54,0x3b,0x72,0x8b,0x03,0x24,0x29,0x23,0x43,0x66,0x22,0x53,0x41,0x11,0x62,0x10};
::memcpy(firstMessage, x, sizeof(x));
}
If you are using C++11, you can also initialize the firstMessage in the class member initialization list as
MessageParsingTest::MessageParsingTest() :
firstMessage{0x24,0x54,0x3b,0x72,0x8b,0x03,0x24,0x29,0x23,0x43,0x66,0x22,0x53,0x41,0x11,0x62,0x10},
...
You can use a temporary buffer and then copy into you member as this:
void MessageParsingTest::setUp() {
unsigned char tmp[1500] = {0x24,0x54,0x3b,0x72,0x8b,0x03,0x24,0x29,0x23,0x43,0x66,0x22,0x53,0x41,0x11,0x62,0x10};
memcpy(firstMessage, tmp, 1500);
}
I'm trying to add new functionality to an existing library. I would need to add new data to a class hierarchy so that the root class would have accessors for it. Anyone should be able to get this data only sub-classes could set it (i.e. public getter and protected setter).
To maintain backward compatibility, I know I must not do any of the following (list only includes actions relevant to my problem):
Add or remove virtual functions
Add or remove member variables
Change type of existing member variable
Change signature of existing function
I can think of two ways to add this data to hierarchy: adding a new member variable to root class or adding pure virtual accessor functions (so that data could be stored in sub-classes). However, to maintain backward compatilibity I can not do either of these.
The library is using extensively pimpl idiom but unfortunately the root class I have to modify does not use this idiom. Sub-classes, however, use this idiom.
Now only solution that I can think of is simulating member variable with static hash-map. So I could create a static hash-map, store this new member to it, and implement static accessors for it. Something like this (in pseudo c++):
class NewData {...};
class BaseClass
{
protected:
static setNewData(BaseClass* instance, NewData* data)
{
m_mapNewData[instance] = data;
}
static NewData* getNewData(BaseClass* instance)
{
return m_mapNewData[instance];
}
private:
static HashMap<BaseClass*, NewData*> m_mapNewData;
};
class DerivedClass : public BaseClass
{
void doSomething()
{
BaseClass::setNewData(this, new NewData());
}
};
class Outside
{
void doActions(BaseClass* action)
{
NewData* data = BaseClass::getNewData(action);
...
}
};
Now, while this solution might work, I find it very ugly (of course I could also add non-static accessor functions but this wouldn't remove the ugliness).
Are there any other solutions?
Thank you.
You could use the decorator pattern. The decorator could expose the new data-elements, and no change to the existing classes would be needed. This works best if clients obtain their objects through factories, because then you can transparently add the decorators.
Finally, check binary compatibility using automated tools like abi-compliance-checker.
You can add exported functions (declspec import/export) without affecting binary compatibility (ensuring you do not remove any current functions and add your new functions at the end), but you cannot increase the size of the class by adding new data members.
The reason you cannot increase the size of the class is that for someone that compiled using the old size but uses the newly extended class would mean that the data member stored after your class in their object (and more if you add more than 1 word) would get trashed by the end of the new class.
e.g.
Old:
class CounterEngine {
public:
__declspec(dllexport) int getTotal();
private:
int iTotal; //4 bytes
};
New:
class CounterEngine {
public:
__declspec(dllexport) int getTotal();
__declspec(dllexport) int getMean();
private:
int iTotal; //4 bytes
int iMean; //4 bytes
};
A client then may have:
class ClientOfCounter {
public:
...
private:
CounterEngine iCounter;
int iBlah;
};
In memory, ClientOfCounter in the old framework will look something like this:
ClientOfCounter: iCounter[offset 0],
iBlah[offset 4 bytes]
That same code (not recompiled but using your new version would look like this)
ClientOfCounter: iCounter[offset 0],
iBlah[offset 4 bytes]
i.e. it doesn't know that iCounter is now 8 bytes rather than 4 bytes, so iBlah is actually trashed by the last 4 bytes of iCounter.
If you have a spare private data member, you can add a Body class to store any future data members.
class CounterEngine {
public:
__declspec(dllexport) int getTotal();
private:
int iTotal; //4 bytes
void* iSpare; //future
};
class CounterEngineBody {
private:
int iMean; //4 bytes
void* iSpare[4]; //save space for future
};
class CounterEngine {
public:
__declspec(dllexport) int getTotal();
__declspec(dllexport) int getMean() { return iBody->iMean; }
private:
int iTotal; //4 bytes
CounterEngineBody* iBody; //now used to extend class with 'body' object
};
If your library is open-source then you can request to add it to the upstream-tracker. It will automatically check all library releases for backward compatibility. So you can easily maintain your API.
EDIT: reports for qt4 library are here.
It is hard to maintain binary compatibility - it is much easier to maintain only interface compatibility.
I think that the only reasonable solution is to break supporting current library and redesign it to only export pure virtual interfaces for classes.
That interfaces could never be modified in the future, but you can add new interfaces.
In that interfaces you could only use primitive types like pointers and specified size integers or floats. You should not have interfaces with for example std::strings or other non-primitive types.
When returning pointers to data allocated in DLL, you need to provide a virtual method for deallocation, so that the application deallocates the data using DLL's delete.
Adding data members to the root will break binary compatibility (and force a rebuild, if that is your concern), but it won't break backward compatibility and neither will adding member functions (virtual or not). Adding new member functions is the obvious way to go.
I have the following struct which will be used to hold plugin information. I am very sure this will change (added to most probably) over time. Is there anything better to do here than what I have done assuming that this file is going to be fixed?
struct PluginInfo
{
public:
std::string s_Author;
std::string s_Process;
std::string s_ReleaseDate;
//And so on...
struct PluginVersion
{
public:
std::string s_MajorVersion;
std::string s_MinorVersion;
//And so on...
};
PluginVersion o_Version;
//For things we aren't prepared for yet.
void* p_Future;
};
Further, is there any precautions I should take when building shared objects for this system. My hunch is I'll run into lots of library incompatibilities. Please help. Thanks
What about this, or am I thinking too simple?
struct PluginInfo2: public PluginInfo
{
public:
std::string s_License;
};
In your application you are probably passing around only pointers to PluginInfos, so version 2 is compatible to version 1. When you need access to the version 2 members, you can test the version with either dynamic_cast<PluginInfo2 *> or with an explicit pluginAPIVersion member.
Either your plugin is compiled with the same version of C++ compiler and std library source (or its std::string implementation may not be compatible, and all your string fields will break), in which case you have to recompile the plugins anyway, and adding fields to the struct won't matter
Or you want binary compatibility with previous plugins, in which case stick to plain data and fixed size char arrays ( or provide an API to allocate the memory for the strings based on size or passing in a const char* ), in which case it's not unheard of to have a few unused fields in the struct, and then change these to be usefully named items when the need arises. In such cases, it's also common to have a field in the struct to say which version it represents.
But it's very rare to expect binary compatibility and make use of std::string. You'll never be able to upgrade or change your compiler.
As was said by someone else, for binary compatibility you will most likely restrict yourself to a C API.
The Windows API in many places maintains binary compatibility by putting a size member into the struct:
struct PluginInfo
{
std::size_t size; // should be sizeof(PluginInfo)
const char* s_Author;
const char* s_Process;
const char* s_ReleaseDate;
//And so on...
struct PluginVersion
{
const char* s_MajorVersion;
const char* s_MinorVersion;
//And so on...
};
PluginVersion o_Version;
};
When you create such a beast, you need to set the size member accordingly:
PluginInfo pluginInfo;
pluginInfo.size = sizeof(pluginInfo);
// set other members
When you compile your code against a newer version of the API, where the struct has additional members, its size changes, and that is noted in its size member. The API functions, when being passed such a struct presumably will first read its size member and branch into different ways to handle the struct, depending on its size.
Of course, this assumes that evolution is linear and new data is always only added at the end of the struct. That is, you will never have different versions of such a type that have the same size.
However, using such a beast is a nice way of ensuring that user introduce errors into their code. When they re-compile their code against a new API, sizeof(pluginInfo) will automatically adapt, but the additional members won't be set automatically. A reasonably safety would be gained by "initializing" the struct the C way:
PluginInfo pluginInfo;
std::memset( &pluginInfo, 0, sizeof(pluginInfo) );
pluginInfo.size = sizeof(pluginInfo);
However, even putting aside the fact that, technically, zeroing memory might not put a reasonable value into each member (for example, there could be architectures where all bits set to zero is not a valid value for floating point types), this is annoying and error-prone because it requires three-step construction.
A way out would be to design a small and inlined C++ wrapper around that C API. Something like:
class CPPPluginInfo : PluginInfo {
public:
CPPPluginInfo()
: PluginInfo() // initializes all values to 0
{
size = sizeof(PluginInfo);
}
CPPPluginInfo(const char* author /* other data */)
: PluginInfo() // initializes all values to 0
{
size = sizeof(PluginInfo);
s_Author = author;
// set other data
}
};
The class could even take care of storing the strings pointed to by the C struct's members in a buffer, so that users of the class wouldn't even have to worry about that.
Edit: Since it seems this isn't as clear-cut as I thought it is, here's an example.
Suppose that very same struct will in a later version of the API get some additional member:
struct PluginInfo
{
std::size_t size; // should be sizeof(PluginInfo)
const char* s_Author;
const char* s_Process;
const char* s_ReleaseDate;
//And so on...
struct PluginVersion
{
const char* s_MajorVersion;
const char* s_MinorVersion;
//And so on...
};
PluginVersion o_Version;
int fancy_API_version2_member;
};
When a plugin linked to the old version of the API now initializes its struct like this
PluginInfo pluginInfo;
pluginInfo.size = sizeof(pluginInfo);
// set other members
its struct will be the old version, missing the new and shiny data member from version 2 of the API. If it now calls a function of the second API accepting a pointer to PluginInfo, it will pass the address of an old PluginInfo, short one data member, to the new API's function. However, for the version 2 API function, pluginInfo->size will be smaller than sizeof(PluginInfo), so it will be able catch that, and treat the pointer as pointing to an object that doesn't have the fancy_API_version2_member. (Presumably, internal of the host app's API, PluginInfo is the new and shiny one with the fancy_API_version2_member, and PluginInfoVersion1 is the new name of the old type. So all the new API needs to do is to cast the PluginInfo* it got handed be the plugin into a PluginInfoVersion1* and branch off to code that can deal with that dusty old thing.)
The other way around would be a plugin compiled against the new version of the API, where PluginInfo contains the fancy_API_version2_member, plugged into an older version of the host app that knows nothing about it. Again, the host app's API functions can catch that by checking whether pluginInfo->size is greater than the sizeof their own PluginInfo. If so, the plugin presumably was compiled against a newer version of the API than the host app knows about. (Or the plugin write failed to properly initialize the size member. See below for how to simplify dealing with this somewhat brittle scheme.)
There's two ways to deal with that: The simplest is to just refuse to load the plugin. Or, if possible, the host app could work with this anyhow, simply ignoring the binary stuff at the end of the PluginInfo object it was passed which it doesn't know how to interpret.
However, the latter is tricky, since you need to decide this when you implement the old API, without knowing exactly what the new API will look like.
what rwong suggest (std::map<std::string, std::string>) is a good direction. This is makes it possible to add deliberate string fields. If you want to have more flexibility you might declare an abstract base class
class AbstractPluginInfoElement { public: virtual std::string toString() = 0;};
and
class StringPluginInfoElement : public AbstractPluginInfoElement
{
std::string m_value;
public:
StringPluginInfoElement (std::string value) { m_value = value; }
virtual std::string toString() { return m_value;}
};
You might then derive more complex classes like PluginVersion etc. and store a map<std::string, AbstractPluginInfoElement*>.
One hideous idea:
A std::map<std::string, std::string> m_otherKeyValuePairs; would be enough for the next 500 years.
Edit:
On the other hand, this suggestion is so prone to misuse that it may qualify for a TDWTF.
Another equally hideous idea:
a std::string m_everythingInAnXmlBlob;, as seen in real software.
(hideous == not recommended)
Edit 3:
Advantage: The std::map member is not subject to object slicing. When older source code copies an PluginInfo object that contains new keys in the property bag, the entire property bag is copied.
Disadvantage: many programmers will start adding unrelated things to the property bag, and even starts writing code that processes the values in the property bag, leading to maintenance nightmare.
Here's an idea, not sure whether it works with classes, it for sure works with structs: You can make the struct "reserve" some space to be used in the future like this:
struct Foo
{
// Instance variables here.
int bar;
char _reserved[128]; // Make the class 128 bytes bigger.
}
An initializer would zero out whole struct before filling it, so newer versions of the class which would access fields that would now be within the "reserved" area are of sane default values.
If you only add fields in front of _reserved, reducing its size accordingly, and not modify/rearrange other fields you should be OK. No need for any magic. Older software will not touch the new fields as they don't know about them, and the memory footprint will remain the same.