Skip to content
zplutor edited this page Feb 9, 2023 · 1 revision

Zaf defines an object system to provide limited dynamic and reflection abilities to classes. This article introduces its basic concepts and usage.

Foundation Classes

Like other object-oriented programming languages, zaf defines a root base class zaf::Object, which is derived by all classes who want to join the object system and make use of its abilities. zaf::Window and zaf::Control are base classes to window and control separately, they are both derived from zaf::Object, therefore all windows and controls in zaf join the object system.

zaf::Object has a static constant pointer Type points to a zaf::ObjectType object, which represents static type information of the class. It can be used to access class meta information such as class name and properties. zaf::Object also has a virtual GetType() method to get a same type pointer that represents dynamic type information of the object. Here is an exmple of zaf::ImageBox class, it is derived from zaf::Control which is derived from zaf::Objecct :

//Output type information of classes.
std::wcout << zaf::Object::Type->GetName() << std::endl;   //Output "Object"
std::wcout << zaf::Control::Type->GetName() << std::endl;  //Output "Control"
std::wcout << zaf::ImageBox::Type->GetName() << std::endl; //Output "ImageBox"

//Output type information of a zaf::ImageBox object.
std::shared_ptr<zaf::ImageBox> image_box = zaf::Create<zaf::ImageBox>();
std::wcout << image_box->Type->GetName() << std::endl;      //Output "ImageBox"
std::wcout << image_box->GetType()->GetName() << std::endl; //Output "ImageBox"

std::shared_ptr<zaf::Control> control = image_box;
std::wcout << control->Type->GetName() << std::endl;      //Output "Control"
std::wcout << control->GetType()->GetName() << std::endl; //Output "ImageBox"

std::shared_ptr<zaf::Object> object = image_box;
std::wcout << object->Type->GetName() << std::endl;      //Output "Object"
std::wcout << object->GetType()->GetName() << std::endl; //Output "ImageBox"

zaf::ObjectType has GetBase() mehtod to get base class’ type information. The relationship of these classes is shown in the picture below:

Note that the three classes derived from zaf::ObjectType are internal and are invisible to users, they can be accessed only with zaf::ObjectType interfaces.

Object Creating and Initialization

In zaf, objects are typically dynamic allocated and are manged by std::shared_ptr . Besides constructors, objects have further more initialization steps, therefore they must be created using zaf::Create() function, otherwise they might not be initialized properly.

During initialization, two virtual methods of zaf::Object is called in order:

  • Initialize() , called right after consturctor.
  • AfterParse() , called after parsing associated XAML (this is another topic which will be introduced in the future).

Derived classes may override these two methods to do extra initialization they needed. For example, a window class may create and add controls into window in Initialize() .

A few objects are not necessarily be dynamic allocated and thus no need to use zaf::Create() to create them. For example, zaf::Rect , zaf::Size , zaf::Point are typically stack allocated, and they have nothing to do in Initialize() or AfterParse() .

Making a Class Join the Object System

To make a class join the object system, you need to follow the following steps:

  • Make the class be derived from zaf::Object.
  • Add macro ZAF_DECLARE_TYPE in the class’ public section, to declare that this class is joined the object system. This macro declares necessarily interfaces to the class.
  • In .cpp, use macros ZAF_DEFINE_TYPE() and ZAF_DEFINE_TYPE_END to define implementation of declared interfaces.

Here is an example:

//MyObject.h
#include <zaf/object/object.h>

class MyObject : public zaf::Object {
public:
    ZAF_DECLARE_TYPE;
};

//MyObject.cpp
#include "MyObject.h"
#include <zaf/object/type_definition.h>

ZAF_DEFINE_TYPE(MyObject)
ZAF_DEFINE_TYPE_END;

Object Equality

There are two virtual methods IsEqual() and Hash() in zaf::Object to provide object equality ability.

The default implementation of IsEqual() checks if two objects are equal by their memory address. Derived classes can override this method to implement their own equality strategy.

The default implementation of Hash() uses std::hash<> to calculate the hash value of object’s memory address. Derived classes can override this method to implement their own hash strategy.

If a class has already defined operator== and specialized std::hash<> , it can use ZAF_DECLARE_EQUALITY and ZAF_DEFINE_EQUALITY() macros to simplify the definition of IsEqual() and Hash() . For example:

//MyObject.h
#include <zaf/object/equality.h>
#include <zaf/object/object.h>

class MyObject : public zaf::Object {
public:
    ZAF_DECLARE_TYPE;

    //Declare IsEqual() and Hash().
    ZAF_DECLARE_EQUALITY;
};

//Already defined operator==.
inline bool operator==(const MyObject&, const MyObject&) {
    //...
}

//Already specialized std::hash<>.
namespace std {
template<>
struct hash<MyObject> {
    std::size_t operator()(const MyObject&) {
        //...
    }
};
}

//MyObject.cpp
#include "MyObject.h"
#include <zaf/object/type_definition.h>

ZAF_DEFINE_TYPE(MyObject)
ZAF_DEFINE_TYPE_END;

//Define implementation of IsEqual() and Hash().
ZAF_DEFINE_EQUALITY(MyObject);