Class Inheritance with Eolian

The Creating New Classes tutorial explained how to define new classes using Eolian. New classes don't need to start from scratch however. In fact, it is common practice in Object-oriented programming (OOP) to extend the functionality of an existing class by inheriting from it and creating a new one.

This tutorial shows how to inherit from a class in Eolian. It also describes how derived classes can access their parent's public and private data.

Prerequisites

Step One: Creating a Derived Class

Copy all the example_rectangle.* files you created in the Creating New Classes with Eolian tutorial. There should be 4 of them: The Eolian file (.eo), the implementation file (.c) and two autogenerated files (.eo.h and .eo.c). Make sure to copy the main file (eo_classes_main.c) and rename it to eo_inherit_main.c for consistency with the name of this tutorial.

Now you will create a new class, named Example.Square which will inherit from Example.rectangle. The theory states that squares are a particular kind of rectangles in which the width and the height are equal. Therefore, the Example.Square class will override Example.Rectangle's width and height setters to ensure that those two variables have always the same value.

Start describing your new class with a new Eolian file named example_square.eo:

class Example.Square extends Example.Rectangle {
   implements {
      Example.Rectangle.width {set;}
      Example.Rectangle.height {set;}
   }
}

As you can see this is derived from Example.Rectangle. Regular classes must be derived from Efl.Object but in this case you get that automatically, since Example.Rectangle is already derived from Efl.Object.

You can also notice that this class does not provide any new method or property (there is no methods block). It only implements two properties, which currently belong to its parent class (Example.Rectangle.width and Example.Rectangle.height). Furthermore only the setters for these properties are implemented: Reads of these properties will be routed to the getter in the parent class.

Next turn the Eolian description into C files with the eolian_gen command (as seen in the previous tutorial). Be careful, as there's a new parameter in use in this example:

eolian_gen -gchi example_square.eo -I .

The -I parameter tells eolian_gen where to look for other Eolian files, in case it needs the description of a class it does not know about. In this tutorial, Example.Square needs the description of Example.Rectangle which resides in the example_rectangle.eo file. This file is in the same folder as example_square.eo, the current folder, therefore, the final command requires a -I . (The dot indicates the current folder).

This will create the boilerplate files (.eo.h and .eo.c) and the implementation file, which you will complete in the next step.

Step Two: Implementing the Derived Class

Edit the implementation file example_square.c. It should contain:

  • An empty structure Example_Square_Data. This will remain empty, because squares do not add any additional data to rectangles.
  • A method called _example_square_example_rectangle_width_set(). This is the setter for the width property, inherited from the parent Example.Rectangle class. The name of the method contains all the ancestry information. Don't worry, though, you will not be calling this method directly.
  • A method called _example_square_example_rectangle_height_set(). As above, this is the setter for the height property.

The implementation of these methods requires calling the parent class, therefore, you need to include the parent's class header file. Add a line after the first block of #includes in the file:

#include "example_rectangle.eo.h"

You now need to focus on implementing these setters. They both need to set the width and height internal variables to the same value. These variables are private to Example.Rectangle, so Example.Square has no direct access to them. However, they can be modified through the setters from Example.Rectangle.

A small caveat, though: The parent's setters are being overridden by the derived class, so if you try to call them directly from the overridden code, you will end up in an infinite loop! Keep reading.

In EFL when you need to call a method from your parent instead of your overridden version you can use efl_super(). Its first parameter is the object and the second is the class whose parent you want to call. Write these implementations for the setters and all will become clear:

EOLIAN static void
_example_square_example_rectangle_width_set(Eo *obj, Example_Square_Data *pd EINA_UNUSED, int width)
{
   example_rectangle_width_set(efl_super(obj, EXAMPLE_SQUARE_CLASS), width);
   example_rectangle_height_set(efl_super(obj, EXAMPLE_SQUARE_CLASS), width);
}
 
EOLIAN static void
_example_square_example_rectangle_height_set(Eo *obj, Example_Square_Data *pd EINA_UNUSED, int height)
{
   example_rectangle_width_set(efl_super(obj, EXAMPLE_SQUARE_CLASS), height);
   example_rectangle_height_set(efl_super(obj, EXAMPLE_SQUARE_CLASS), height);
}

Note:

  • The width and height variables are set through the regular setters you already used in the previous tutorial (example_rectangle_width_set() and example_rectangle_height_set()).
  • The object being passed to the setters, though, is the output of efl_super().
  • You want to call the parent of Example.Square, so the second parameter to efl_supper() is EXAMPLE_SQUARE_CLASS.
  • These setters do not use the Example_Square_Data private data (that structure is actually empty, as seen above), so EINA_UNUSED is used to avoid compiler warnings.

The efl_super() method can also be used to access older ancestors of your class but this is a complex operation and so is outside the scope of this tutorial.

Having written these setters your derived Example.Square class is complete. The next step adds code that uses it.

Step Three: Using the Derived Class

Open up eo_inherit_main.c and start by adding the include for Example.Square:

#include "example_square.eo.h"

Add another method to instantiate your new class, right after _rect_create():

Example_Square *
_square_create()
{
   Example_Square *square;
 
   square = efl_new(EXAMPLE_SQUARE_CLASS,
                    efl_name_set(efl_added, "Square"),
                    example_rectangle_width_set(efl_added, 7));
 
   return square;
}

Note how the side of the rectangle is set with example_rectangle_width_set(). You could also set the height since both setters have the same effect on a square.

Back in the main function declare a variable to hold your new object:

   Eo *rectangle, *square;

Next call _square_create() and print some information, immediately after doing the same thing for the rectangle object:

   square = _square_create();
 
   printf("Square is %dx%d, area is %d\n",
          example_rectangle_width_get(square),
          example_rectangle_height_get(square),
          example_rectangle_area(square));
 
   efl_unref(square);

Notice how you only used Example.Rectangle methods here because Example.Square inherits from it. All methods that work on a rectangle also work on a square.

Remember to dispose of your objects using efl_unref(). Alternatively, you could give them a parent using efl_add() instead of efl_new() and let the parent handle object disposal.

The main program (eo_inherit_main.c) should now look like this:

#define EFL_EO_API_SUPPORT 1
#define EFL_BETA_API_SUPPORT 1
 
#include <Eina.h>
#include <Efl_Core.h>
#include "example_rectangle.eo.h"
#include "example_square.eo.h"
 
Example_Rectangle *
_rect_create()
{
   Example_Rectangle *rectangle;
 
   rectangle = efl_new(EXAMPLE_RECTANGLE_CLASS,
                       efl_name_set(efl_added, "Rectangle"),
                       example_rectangle_width_set(efl_added, 5),
                       example_rectangle_height_set(efl_added, 10));
 
   return rectangle;
}
 
Example_Square *
_square_create()
{
   Example_Square *square;
 
   square = efl_new(EXAMPLE_SQUARE_CLASS,
                    efl_name_set(efl_added, "Square"),
                    example_rectangle_width_set(efl_added, 7));
 
   return square;
}
 
EAPI_MAIN void
efl_main(void *data EINA_UNUSED, const Efl_Event *ev EINA_UNUSED)
{
   Eo *rectangle, *square;
 
   rectangle = _rect_create();
 
   printf("Rectangle is %dx%d, area is %d\n",
          example_rectangle_width_get(rectangle),
          example_rectangle_height_get(rectangle),
          example_rectangle_area(rectangle));
 
   efl_unref(rectangle);
 
   square = _square_create();
 
   printf("Square is %dx%d, area is %d\n",
          example_rectangle_width_get(square),
          example_rectangle_height_get(square),
          example_rectangle_area(square));
 
   efl_unref(square);
 
   efl_exit(0);
}
EFL_MAIN()

If you run now, you should see this in your terminal:

Rectangle is 5x10, area is 50
Square is 7x7, area is 49

Step Four: Accessing the Parent's Private Data

The above implementation for Example.Square's setters works because Example.Rectangle has public setters. That is to say, even though width and height are private variables visible only to Example.Rectangle, they can be accessed by anyone through their setters and getters.

The final step in this tutorial shows you how a derived class can access private data from its parent, which is also a common operation in OOP.

The first thing done in the implementation file example_rectangle.c is to define the Example_Rectangle_Data structure, which is therefore only accessible from that file. If Example.Square has to have access to this structure, it has to be defined in a common header.

Create an example_rectangle_private.h file and move the structure there:

typedef struct
{
   int width, height;
} Example_Rectangle_Data;

In example_rectangle.c, replace the structure with an include :

#include "example_rectangle_private.h"

Finally include the header from example_square.c too. At this point the implementation for Example.Square can interpret its parent's private data Example_Rectangle_Data. You only need to retrieve a pointer to that data using efl_data_scope_get(). This is how your square setters should look now:

EOLIAN static void
_example_square_example_rectangle_width_set(Eo *obj, Example_Square_Data *pd EINA_UNUSED, int width)
{
   Example_Rectangle_Data *rect_pd = efl_data_scope_get(obj, EXAMPLE_RECTANGLE_CLASS);
   rect_pd->width = width;
   rect_pd->height = width;
}

And likewise for the height setter.

Note how the first parameter to efl_data_scope_get() is the object for which you want to retrieve the private data, and the second parameter is the ancestor class. You can retrieve the private data for any class, as long as it belongs to your hierarchy.

NOTE: For performance reasons, no runtime check is performed to ensure that the requested class actually belongs to your ancestry. If you want to avoid undefined behavior use efl_data_scope_safe_get().

Once you have the pointer to the private data of Example.Rectangle you can write to both width and height as you were doing before.

If you need to keep the private data pointer alive for a long time look into efl_data_ref() and efl_data_unref(). These methods will make sure that the enclosing object is not destroyed until you are done working with its private data.

Step Five: Per-Object Method Override

One final function worth knowing is efl_object_override(). It allows changing some method's implementation for a particular object just as derived classes do but on a single object.

Its use case is a bit advanced so it will not be shown in this tutorial. It is related to class inheritance however so is worthy of note.

Summary

In this tutorial, you've learned:

  • Derived classes can be created with Eolian.
  • Methods overridden by the derived class are automatically called when a derived class object is used.
  • Public data of the parent class can be accessed through its public accessors.
  • Private data of the parent class can be accessed through efl_data_scope_get().
  • Method implementations on individual objects can be overridden using efl_object_override().

Further Reading

Creating New Classes
Teaches the basis of class creation with Eo
Multiple Inheritance
Moves forward and explains how to inherit from more than one class at a time