0

I'm working on a type descriptor project in C++11. The type descriptor's job is to know the types of every member in a class, it's size and it's offset from the base of an object. I don't support multiple inheritance as well as objects with virtual methods so I am making this a lot simpler for now. The goal is to be able to serialize and unserialize objects using the descriptor.

Note that this is a pet project to mess around with features like variadic templates, pointer to members and other features of C++ I am not familiar with, so there's no need to point me toward something like boost::archiving. :)

The way I actually register members is very similar to boost::python::class_'s way.

ClassDescriptor fooDesc( "Foo" );
fooDesc.addMember( "a", &Foo:: a );
fooDesc.addMember( "b", &Foo:: b );

// (abridged for clarity) :
template< typename ClassType, typename MemberType >
ClassDescriptor& ClassDescriptor::addMember(
  const char* name,
  MemberType ClassType::* member
)
{
   return addMember< MemberType >( name, reinterpret_cast< size_t >( &(((ClassType*)0)->*member)) );
}

Unfortunately, the pointer-to-member feature of C++ can't be used with references in C++, as I've learned earlier this week : https://stackoverflow.com/a/8336479/1074536, so I am not able to use &Foo::refToAndInt for example.

As far as how I am computing the offset of the member, I am not using the offset of macro since my classes will not always be PODs.

So since I can't use pointer-to-members for computing the offset of references, I thought I'd try :

&(((Foo*)nullptr)->refToAnInt)

but has it has been pointed out in another stack overflow thread, this is undefined behavior and on obviously on LLVM it crashes. :(

I'd like to avoid doing stuff like taking the previous member's offset, adding it's size and then somehow computing the padding required to align my next member, since it would look messy and error prone

So, I can't use both of those tricks and offsetof is only for PODs. Any suggestion on what I could try next, apart from my other horrible suggestion?

Thanks!

Community
  • 1
  • 1
fronsacqc
  • 320
  • 2
  • 12
  • 2
    Why would you serialize/unserialize references? They must be initialized in the constructor and cannot be changed later anyway. – Roman L Dec 03 '11 at 17:54
  • @7vies : If you treat an object as a simple array of bytes, it becomes very easy to simply consider references as blocks of 8 bytes (on 64-bit) to fill. The idea here is to simply allocate an array of bytes big enough to hold the object in memory and then punch in the values using the class descriptor. I'm not dealing with vtables, so I don't need the constructor for initialization. Obviously, you have to make sure there is no side effect during regular construction so that loading an asset brings the system in the same state as it was before being persisted. – fronsacqc Dec 05 '11 at 04:56
  • as you already have limitations by not supporting vtables I would say just don't support references neither - this is kind of minor limitation with respect to not supporting vtables. By the way, how are you going to unserialize pointers or references? (obviously, you can't just restore them to the same value they had during serialization, as memory location might be different) – Roman L Dec 05 '11 at 23:42
  • In addition to basic member description, there is metadata that explains what the reference/pointer actually means. Take an object and give it an ID. Visit members and persist them one by one. The metadata helps me interpreting pointers/refs as either a pointer TO an object or INSIDE an object. If I'm pointing TO an object, I allocate an object id, store that as my reference and insert that object in a serialization queue. If it's inside an object, I store it as an offset inside an object. There's edge cases, like what if you haven't visited an object yet, but that can be handled. – fronsacqc Dec 06 '11 at 02:34
  • @7vies : vtables are not supported simply because I am focussing on i/o for now. But the vtable pointer is just another value than can be punched it. The only problem with it is that it is not the same at each execution, so I'll have to resort to some runtime vtable retrieval. Not sure how I'll do that. Maybe on init have each object instantiated, capture the vtable and delete the object? Kinda messy. There might be a better way, but I haven't investigated it yet, hence the no vtable support. For now. – fronsacqc Dec 06 '11 at 02:39
  • 1
    Have you considered not treating objects as array of bytes, but calling constructor/destructor instead? It might be an alternative to your solution which would avoid messing with compiler-specific implementation of references. I remember seeing IoC containers for C++, I wonder how they manage all this. – Roman L Dec 06 '11 at 15:50
  • The problem with calling constructors is that I then need to pass around values to initialize the references in the constructor. Given that I already know how many references there are in an object, I could assume (*as in require*) that there is a constructor that initializes all the references. That would also initialize the vtable for me. I had never heard of the IoC term before. I'll read some about it. Thanks. – fronsacqc Dec 06 '11 at 16:20

1 Answers1

0

According to the standard,

  1. Classes

    6 ... A trivially class is a class that has a trivial default constructor (12.1) and ... ...

    10 A POD struct is a class that is both a trivial class and ... ...

12.1 Constructors

5 ...

 A default default constructor for class X is defined as deleted if:

 ...

 - any non-static data member with no brace-or-equal-initializer is of

reference type,

 ...

 A default constructor is trivial if it is neither user-provided nor deleted

and if

 ...

 - for all the non-static data members of its class that are of class type(or

array thereof), each such class has a trivial default constructor.

For sure, any class that has a reference member cannot be POD unless a brace-or-equal-initializer exists, for example:

class A
{
    A &me=*this;
};

If this is the case, you may be able to create specific workaround.

For the problem of

&(((Foo*)nullptr)->refToAnInt)

You can try the following:

std::aligned_storage<sizeof(Foo), std::alignment_of<Foo>::value>::type storage;
&((static_cast<Foo *>(static_cast<void *>(&storage)))->refToAnInt);

Its behavior should be well defined.

Earth Engine
  • 10,048
  • 5
  • 48
  • 78
  • Yeah, I'm starting to think that allocating a stack of bytes with std::aligned_storage is the only way to fix this. It's unfortunate, because I'm going to have to get my API a little bit dirty. Maybe I could also skip support for references. ;) – fronsacqc Dec 05 '11 at 05:11