# Object type erasure ¶

One kind of type is Object , which is complex enough to have its own section.

An object is something that has shared semantics. It can contain methods, signals and properties. It has no data exposed through type erasure (but you can expose accessor methods if you want).

The “signature” of an object is a  MetaObject  , it is unique for all instances of a same object. The  MetaObject  contains a list of  MetaMethod  ,  MetaSignal  and  MetaProperty  . These three classes contain all the information required to characterize their respective entity.

Each method, signal and property has an id, which is fixed and stored in the meta object. Overloading is only supported for methods, over their arguments, as in C++.

Though it may be possible to change the meta object of an object after it has been created, it is not a good idea. This feature has not been well tested and is known not to work on the messaging layer because it caches the meta object to avoid sending them every time.

The  MetaObject  is returned by the  ObjectTypeInterface  . The method receives a  void*  , so different instances of the same type may have different meta objects (this feature is used by the  DynamicObjectTypeInterface  , discussed below).

## Typing ¶

There are two classes that inherit from  ObjectTypeInterface  ,  StaticObjectTypeBase  (which is not a base, this name is misleading) and  DynamicObjectTypeInterface  . It is of course possible to create your own implementation by inheriting from  ObjectTypeInterface  , but these two classes seem to handle all the cases already.

The names may be misleading, but  StaticObjectTypeBase  is for C++ objects and  DynamicObjectInterface  is for all other objects (from other languages, but also objects over the messaging layer). C++ is the only statically typed language that we support for the moment, but I think that if we are to support others, they must be implemented through  DynamicObjectTypeInterface  or have their own class.

With  StaticObjectTypeBase  , the meta object is contained in the type, but with  DynamicObjectTypeBase  , there is only one instance of that class per process and the meta objects are contained in the storage.

### DynamicObjectTypeInterface ¶

 DynamicObjectTypeInterface  is never really used, the class is in the private file dynamicobject.cpp , and the only public access you have to it is  qi::getDynamicTypeInterface  which returns a singleton of that class. The storage corresponding to that class is a  DynamicObject  which contains all the information needed for the type to work. That class is defined in dynamicobject.hpp .

It is usually not necessary to deal with  DynamicObject  either actually. It is possible to use a builder to create a dynamic object. Such a builder create an abstraction that simplifies the task in C++.

The corresponding builder for dynamic objects is  DynamicObjectBuilder  , defined in dynamicobjectbuilder.hpp . It does not expose type interfaces, nor dynamic objects, but directly an  AnyObject  that you can manipulate. There is only one  AnyObject  per  DynamicObjectBuilder  , the  object  method always return the same instance.

 DynamicObjectBuilder  allows you to create an object with C++ functions, signals and properties easily. There is no  this  instance though, it is the user’s task to keep track of it. Here’s an example usage:



struct Data {
int val;
};
auto self = std::make_shared<Data>();

DynamicObjectBuilder builder;

// the builder can't deduce the signature of a lambda (thanks C++), so we
// need an explicit cast
// builders recognize anything AnyFunction recognizes
return self->val;
}));
self->val += x;
}));

auto object = builder.object(); // can specify a destructor here



It is still a bit cumbersome. Dynamic objects like that are only used in tests. You can find a real example of dynamic object usage in the Python bindings in pyobject.cpp .

### StaticObjectTypeBase ¶

Similarly to  DynamicObjectTypeInterface  , you don’t need to deal with  StaticObjectTypeBase  directly. For that too, you use a builder.

The corresponding builder is  ObjectTypeBuilder  (yes, the name is not very explicit), defined in objecttypebuilder.hpp . Its interface is similar to that of  DynamicObjectBuilder  , but has some more features.

First, it is templated on the class you want to register. This allows you to advertise the method pointers of the class directly.

It also has an  inherits  method that declares that your type inherits from another. This allows casting to parent types in type-erased contexts.

Warning

Virtual inheritance is not supported and this method will crash if used in that case.

This method relies on undefined behavior in C++, it was realized by professionals, do not try this at home.

You can see the definition of  inherits  in objecttypebuilder.hxx . It tries to create a pointer to a random address in memory, casts it, and saves the difference, without ever dereferencing it.

The problem with virtual inheritance is that the shift between the base class and the derived class is not fixed and is thus saved somewhere in the derived object’s memory. This means that this cast actually dereferences that invalid pointer and tries to read the address of the base class there.

We couldn’t find how to overcome this limitation in pure C++.

In the end, the  registerType  function must be called. It will register the type in the global map so that every time you use an object of type T, the correct type interface will be retrieved.

## Usage ¶

Objects are usually handled by  AnyObject  , which contains a shared pointer to a  GenericObject  .

### GenericObject ¶

At the lowest level, the object is type-erased as a  GenericObject  , defined in genericobject.hpp . This class contains a  void*  and an  ObjectTypeInterface*  . It does not own the object. It provides all the functions you need to call methods, trigger signals, connect to them and access properties.

It inherits from  Manageable  which adds some hidden methods for tracing and statistics. These methods are also exposed through type erasure.

### AnyObject ¶

 AnyObject  wraps a  GenericObject  . It is defined in object.hxx . It owns the  GenericObject  through a shared pointer and this shared pointer owns the real object thanks to a custom destructor (or shared pointer refcount sharing).

It re-exposes all the methods of  GenericObject  through  GenericObjectBounce  .

### Object ¶

 AnyObject  is actually not a class but only an alias to  Object<qi::Empty>  .

 Object  is defined in object.hxx . It has an  operator->()  that returns the  T  it has been specialized to (after a runtime check in the function  checkT  which can throw).

This allows you to write code like:



qi::Object<MyClass> obj(new MyClass());
obj->directCall();



Assuming  MyClass  is a type registered in the type system.

Futures are not part of the function signature. A function returning a type  T  has the same signature as a function returning a  Future<T>  in the type system.

This implies some complex code to extract the potential future the method returns and unwrapping it. The code to do that is in  GenericObject::call  and  GenericObject::async  , in genericobject.hpp . This task is accomplished by  extractFuture  and  adaptFutureUnwrap  .

There is one more trick to that: futures are registered in the type system, as Objects . This case is explained in Registering template types .