Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Vala different type of constructors

Why, and what does the three vala constructors ?

  • class construct
  • construct
  • method with class name

and more specific, why the 3rd construct is never called when using it from a Gtk.Builder file?

like image 485
Victor Aurélio Avatar asked Oct 07 '15 23:10

Victor Aurélio


1 Answers

Short answer: because that's how GObject works. The long answer is just a smidge longer:

C doesn't have objects or constructors, it has structs and functions. Structs are very simple; they contain fields, and that's it. No methods, constructors, destructors, etc. They look like this:

typedef struct Foo_ {
  int bar;
  char* baz;
} Foo;

To "instantiate" a struct, you allocate the necessary memory (either on the stack or the heap, I'll focus on the heap for the rest of the question) and set the fields.

This quickly gets to be a pain, even for our simple struct, so you'll usually see functions to help out with allocating and freeing structs. Something like this:

Foo* foo_new (int bar, char* baz) {
  Foo* foo = malloc (sizeof (Foo));
  /* malloc() can fail.  Some libraries would return null, some would
     just assume it never does.  GLib-based software generally just exits
     with an error, which is what we'll do here. */
  if (NULL == foo) {
    fprintf (stderr, "%s:%d: Unable to allocate room for struct Foo\n",
             __FILE__, __LINE__);
    exit (EXIT_FAILURE);
  }
  foo->bar = bar;
  foo->baz = (NULL != baz) ? strdup (baz) : NULL;
  return foo;
}

void foo_free (Foo* foo) {
  if (NULL == foo)
    return;

  if (NULL != foo->baz)
    free (foo->baz);
  free (foo);
}

In Vala, the *_new functions are mapped to named constructors. The Vala binding for this might look something like:

[Compact]
public class Foo {
  public Foo ();

  public int bar;
  public string? baz;
}

That's all pretty simple, but what happens when you want to "extend" Foo and add a new field? C doesn't have any language-level support for "extending" a struct. C programmers get around this by embedding the base struct in the child struct:

typedef struct Qux_ {
  struct Foo parent;
  int quux;
} Qux;

This is a pretty decent solution at the C level; the first part of Qux struct is exactly the same as a Foo struct, so when we want to use a Qux as a Foo all we have to do is cast:

void qux_set_bar_and_qux (Qux* qux, int bar, int quux) {
  ((Foo*) qux)->bar = bar;
  qux->quux = quux;
}

Unfortunately it breaks down pretty badly when creating a new instance. Remember that our foo_new function allocates a slice of sizeof(Foo) bytes on the heap (using malloc)—there is no room for the quux field! That means we can't call our foo_new function.

If you're calling a library written in Vala, there is a way around this: in addition to the foo_new function, Vala will actually generate a foo_construct function. So, given something like

[Compact]
public class Foo {
  public Foo (int bar, string? baz) {
    this.bar = bar;
    this.baz = baz;
  }
}

What Vala will actually generate is something a bit like this:

void foo_construct (Foo* foo, int bar, char* baz) {
  foo->bar = bar;
  foo->baz = g_strdup (baz);
}

Foo* foo_new (int bar, char* baz) {
  Foo* foo = g_malloc (sizeof (Foo));
  foo_construct (foo, bar, baz);
  return foo;
}

Now, if our Qux class in Vala subclasses Foo, it can call our Foo named constructor:

[Compact]
public class Qux : Foo {
  public Qux (int quux) {
    base (0, "Hello, world");
    this.quux = quux;
  }

  public int quux;
}

Because the generated code doesn't actually call foo_new, it calls foo_construct:

Qux* qux_new (int quux) {
  Qux* qux = g_malloc (sizeof (Qux));
  foo_construct ((Foo*) qux, 0, "Hello, world");
  qux->quux = quux;
}

Sadly, code not written in Vala rarely follows this convention (grep for the 'has_construct_function' CCode attribute in the VAPIs distributed with valac).

At this point you might be thinking, "It's a pain, but why not just re-create the contents of the foo_new function in qux_new". Well, because you may not have access to the contents of the Foo struct. The person who wrote Foo may not want you messing with their private fields, so they can make Foo an incomplete type in the public headers, and keep the full definition to themselves.

Now, let's start talking about GObject properties. I'm going to be a bit light on the details, but basically it allows you to register types, and includes a bit of information about them which is available at runtime.

Classes registered with GObject can have properties. These are conceptually somewhat similar to fields in C structs, but the type provides callbacks for loading and storing them instead of just letting your code store to an address directly. This also means it can do thinks like emit a signal when you set a value, and some other convenient stuff.

Class initialization in GObject is fairly complicated. We'll talk about that a bit more in a minute, but let's first look at it from the point of view of a library which wants to instantiate a GObject class. That would look something like this:

Qux* qux = g_object_new (QUX_TYPE,
                         "bar", 1729,
                         "baz", "Hello, world",
                         "quux", 1701,
                         NULL);

It's probably pretty obvious what this does: it creates a Qux instance, and sets the "bar" property to 1729, "baz" to "Hello, world", and "quux" to 1701. Now, back to how the class is instantiated. Again, this is (more than) a little simplified, but…

First, enough memory to hold the Qux instance (including the Foo parent class, and now also the GObject class which is the ancestor of all GObjects) is allocated.

Next, the callbacks to set the "bar", "baz", and "qux" properties are invoked.

Next, the *_constructor function is called. In Vala, this is mapped to the construct block. It looks something like this:

static GObject * foo_constructor (GType type,
    guint n_construct_properties,
    GObjectConstructParam construct_properties[n_construct_properties]) {
  GObjectClass * parent_class = G_OBJECT_CLASS (foo_parent_class);
  GObject * obj = parent_class->constructor (type, n_construct_properties, construct_properties);
  Foo * self = G_TYPE_CHECK_INSTANCE_CAST (obj, TYPE_FOO, Foo);

  /* The code from your construct block goes here */

  return obj;
}

Note that you don't get to control the arguments to this function. As you can see, each constructor calls the constructor of its parent class. I've added a comment where the code from your construct block goes; we'll get back to why that is separate from the named constructor soon.

Now, let's look at the code for a named constructor. Remember, the vast majority of libraries don't have a *_construct function, so we'll imagine one which doesn't (for our Qux class):

Qux* qux_new (int bar, int quux) {
  Qux* qux = g_object_new (QUX_TYPE,
                           "bar", bar,
                           "quux", quux,
                           NULL);
  /* Code from your named constructor goes here. */
}

At last, we get to why your named constructor isn't called when using GtkBuilder: it doesn't call qux_new, it calls g_object_new. Calling qux_new is an enormous pain without knowledge of your library, and obviously it's not feasible for GtkBuilder to know about your library.

Finally, let's talk about class construct blocks. They're basically an entirely different thing. Luckily, it doesn't take nearly as long to explain them: they are called when the type is registered with GObject, not when an instance of the type is instantiated. Basically, it gets called the first time your class is instantiated and never again. A quick example:

public class Foo : GLib.Object {
  class construct {
    GLib.message ("Hello, world!");
  }

  construct {
    GLib.message ("%d", this.bar);
  }

  public int bar { get; set; default = 1729; }
}

private static int main (string[] args) {
  var foo = new Foo ();
  foo = new Foo ();

  return 0;
}

Will output

Hello, world!
1729
1729
like image 159
nemequ Avatar answered Oct 18 '22 06:10

nemequ