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.
Example.Rectangle
class developed in Creating New Classes with Eolian.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.
Edit the implementation file example_square.c
. It should contain:
Example_Square_Data
. This will remain empty, because squares do not add any additional data to rectangles._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._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 #include
s 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:
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()
).efl_super()
.Example.Square
, so the second parameter to efl_supper()
is EXAMPLE_SQUARE_CLASS
.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.
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_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
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.
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.
In this tutorial, you've learned:
efl_data_scope_get()
.efl_object_override()
.