What is best way to implement address-albe fields in C++?

111 Views Asked by At

I'm trying to implement deserialization where the mapping to field/member is only known at runtime (its complicated). Anyway what I'm trying to do is something like the following:

Class A
{
  public:
    int a;    // index 0
    float b;  // index 1
    char c;   // index 2
} 

Then I have two arrays, one with the index of the field and the other with something that indicates the type. I then want to iterate over the arrays and write to the fields from a byte stream.

Sorry for the crappy description but I just don't know how to implement it in code. Any ideas would be appreciated thanks!

4

There are 4 best solutions below

0
On BEST ANSWER

Yes you can, the there are two things you need to look out for when doing it though.

First of all make sure you start writing from (const char*)&A.a because all compilers append stuff that doesn't really concern you at the start of an object (visualc puts the vtable there for instance) and you won't be writing what you think you are if you start from the address of the object.

Second you might want to do a #pragma pack(1) before declaring any class that needs to be written to disk because the compilers usually align class members to make DMA transfers more efficient and you might end up having problems with this as well.

On the dynamic part of it, if making one class definition for each field combination you want to have is acceptable, then it's ok to do it like this, otherwise you'd be better off including a hash table in your class and serializing/deserializing its' contents by writing key-value pairs to the file

5
On

Let the compiler do it:

Write an operator>> function.

0
On

There are a lot of issues and decisions needed. At the simplest, you could keep an offset into A per field, you can switch on type and set through a pointer to the field. For example - assuming there's a int16_t encoding field numbers in the input stream, making no effort to use static_cast<> etc. where it's a little nicer to do so, and assuming a 0 field number input terminator...

A a;
char* pa = (char*)&a;
char* p_v = (char*)&input_buffer;

...
while ((field_num = *(int16_t)p_v) && (p_v += sizeof(int16_t)))
    switch (type[field_num])
    {
      case Int32:
        *(int32_t*)(p_a + offset[field_num]) = *(int32_t*)(p_v);
        p_v += sizeof(int32_t);
        break;
      ...
    }

You may want to consider using e.g. ntohl() etc. to handle endianness conversions.

0
On

I can't think of a language construct that will be able to give your a field address given an index at runtime. If you could have the "type" array to actually include field sizes you would have been able to do something like:

istream &in = <get it somehow>;
size_t *field_size = <get it somehow>;
size_t num_of_fields = <get it somehow>;
A a;

char *ptr = reinterpret_cast<char *>(&a);
for (int i = 0; i < num_of_fields; i++)
{
    in.read(ptr, field_size[i]);
    ptr += field_size[i];
}

Note that this will be true if your class is simple and doesn't have any virtual function members (or inheritcs from such a class). If that is the case, you would do better to include a dummy member for getting to the byte offset where fields start within the class:

class A
{
    int __dummy;   /* must be the first data member in the class */

    ...
    <rest of your class definition here>
};

and now change the initialization of ptr as follows:

ptr = reinterpret_cast<char *>(&a) + offsetof(A, __dummy);

Another implicit assumption for this code is that machine byte-order is the same for both the machine running this code and the machine from which the serialized data is received. If not, then you will need to convert the byte ordering of the data read from the stream. This conversion is of course type dependent but you could have another array of conversion functions per field.