Object Construction



On 23 Jul 2000, Owen Taylor wrote:

> Tim Janik <timj@gtk.org> writes:

> > - don't use g_type_create_instance() but g_object_new() instead.
> 
> I'm not in a position to disagree, but, some day, you are going to
> have to explain this one ;-)

ok ;)

we have new type sytsem in glib, a new parameter system, and moved the
object system basics there. i've tried to fix the prominent problems with
GtkArgs by introducing GParamSpec, and removed some limitations of the
type system, such as the inability to destruct classes.
the remaining issues i have with the new object system are

1) in the signal system, i'm working on those currenly (thankfully, with
   lots of input from kenelson)
2) creation of objects that need construct arguments
3) no sane support for singleton objects
4) some people need a class hook to monitor object creation, after/when
   the object is fully constructed
5) no support for abstract classes (somehow, an often requested feature,
   the best thing to do here might be to extend the parameter list
   of g_type_register_* to feature a flag that indicates abstract classes)

for the duration of this email, i'm going to concentrate on a solution
for 2), 3) and 4).
in 1.2, there are many cludges to get object construction to basically
work, somtimes we still end up with unconstructed obejcts and thus have
hacks like:

void
gtk_container_add (GtkContainer *container,
                   GtkWidget    *widget)
{
  g_return_if_fail (GTK_IS_CONTAINER (container));
  g_return_if_fail (GTK_IS_WIDGET (widget));
  g_return_if_fail (widget->parent == NULL);

  if (!GTK_OBJECT_CONSTRUCTED (widget))
    gtk_object_default_construct (GTK_OBJECT (widget));
  gtk_signal_emit (GTK_OBJECT (container), container_signals[ADD], widget);
}

one of the problems is people (especially language binders) using
gtk_type_new() instead of gtk_object_new(). another problem (the real
reason why language binders often use gtk_type_new()) is people putting
stuff into gtk/gnome_<widget>_new() functions, without providing proper
access to that stuff for derived types, e.g. by not exposing _new() parameters
as CONSTRUCT arguments in the argument system. basically, for some language
bindings, objects _have_ to be derivable, so the object can't be
gtk/gnome_<widget>_new()ed, and the construction arguments have to be slid
in through a different interface. providing gtk/gnome_<widget>_construct()
variants is only half of a solution, generic code like GLE (and in a perfect
world: Glade) can't make use of that.

gtk_type_new()/g_type_create_instance() can be thought of as the
the g_malloc (sizeof (MyStruct)) equivalent of a strcuture initialization,
while gtk_object_new()/g_object_new() amount to the additional MyStruct.foo=5;
intiialization. that is:

- gtk_type_new()/g_type_create_instance() create unconstructed objects
- gtk_object_new()/g_object_new() create constructed objects

the way construction from gtk_object_new() works, without actually supplying
the construction arguments, e.g. gtk_object_new (GTK_TYPE_CLIST, NULL), is
by using default construction values. for 1.2, in lack of default values being
registered with the argument system, the default value is always going to
be 0, NULL or 0.0, depending on the argument type.

now there are still some internal issues with construction arguments.
first, 1.2 arguments are flagged, according to:

GTK_ARG_READABLE       = 1 << 0,  argument can be read, e.g. gtk_object_get()
GTK_ARG_WRITABLE       = 1 << 1,  argument can be set, e.g. gtk_object_set()
GTK_ARG_CONSTRUCT      = 1 << 2,  argument can be set and is required at object
                                  creation time. i.e. gtk_object_set() is
                                  always possible, and at gtk_object_new() time
                                  it'll be supplied as well (if necessary, as 0,
                                  NULL or 0.0)
GTK_ARG_CONSTRUCT_ONLY = 1 << 3,  argument will only be set at gtk_object_new()
                                  time, it's not writable after construction

this simply reflects that some object properties can only be written or read,
that some are required at construction time, and that some are not alterable
after constrcution time.
now for objects that only require a single construction argument, this works
reasonably well, in the GtkObjectClass.set_arg implementation we simply
special case the construct argument and do construction from there:

static void
gtk_clist_set_arg (GtkObject      *object,
                   GtkArg         *arg,
                   guint           arg_id)
{
  GtkCList *clist;

  clist = GTK_CLIST (object);

  switch (arg_id)
    {
    case ARG_N_COLUMNS: /* construct-only arg, only set when !GTK_CONSTRUCTED */
      gtk_clist_construct (clist, MAX (1, GTK_VALUE_UINT (*arg)), NULL);
      break;
    case ARG_SHADOW_TYPE:
      gtk_clist_set_shadow_type (clist, GTK_VALUE_ENUM (*arg));
      break;

but if our object indeed requires more than one construction argument,
things get way more trickier. basically, in set_arg() we'll have to keep
track of whether all construction arguments were supplied before we
can actually perform the final step of construction:

static void
gtk_ctree_set_arg (GtkObject      *object,
                   GtkArg         *arg,
                   guint           arg_id)
{
  GtkCTree *ctree;

  ctree = GTK_CTREE (object);

  switch (arg_id)
    {
    case ARG_N_COLUMNS: /* construct-only arg, only set when !GTK_CONSTRUCTED */
      if (ctree->tree_column)
        gtk_ctree_construct (ctree,
                             MAX (1, GTK_VALUE_UINT (*arg)),
                             ctree->tree_column, NULL);
      else
        GTK_CLIST (ctree)->columns = MAX (1, GTK_VALUE_UINT (*arg));
      break;
    case ARG_TREE_COLUMN: /* construct-only arg, only set when !GTK_CONSTRUCTED */
      if (GTK_CLIST (ctree)->columns)
        gtk_ctree_construct (ctree,
                             GTK_CLIST (ctree)->columns,
                             MAX (1, GTK_VALUE_UINT (*arg)),
                             NULL);
      else
        ctree->tree_column = MAX (1, GTK_VALUE_UINT (*arg));
      break;
    case ARG_INDENT:
      gtk_ctree_set_indent (ctree, GTK_VALUE_UINT (*arg));
      break;

this is a bad cludge which just over-complicates the widget implementation.
the reason we have to do this is that there's no implementation entry point
at the time where gtk_object_new() has set all construction arguments, but
hasn't returned yet.
unfortunately, that's exactly the time where we can be sure to be
in a state sane enough to actually perform construction tasks.

before i come to the aproach GObject will take in this regard, lemme briefly
reiterate how parameters work in GLib 2.0, compared to arguments in Gtk+ 1.2:

static void
gtk_ctree_class_init (GtkCTreeClass *klass)
{
  [...]
  gtk_object_add_arg_type ("GtkCTree::n_columns",
                           GTK_TYPE_UINT,
                           GTK_ARG_READWRITE | GTK_ARG_CONSTRUCT_ONLY,
                           ARG_N_COLUMNS);
  [...]
}

becomes:

static void
gtk_ctree_class_init (GtkCTreeClass *klass)
{
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
  [...]
  g_object_class_install_param (gobject_class,
                                ARG_N_COLUMNS,
                                g_param_spec_uint ("n_columns", /* name */
                                                  _("# Columns"), /* nick */
      /* blurb, e.g. for Glade tooltips */        _("Number of Columns in the tree"),
                                                   1, /* minimum */
                                                   128, /* maximum */
                                                   4, /* default value */
                                                   (G_PARAM_READWRITE |
                                                    G_PARAM_CONSTRUCT_ONLY));
}

and

static void
gtk_ctree_set_arg (GtkObject      *object,
                   GtkArg         *arg,
                   guint           arg_id)
{
  GtkCTree *ctree;

  ctree = GTK_CTREE (object);

  switch (arg_id)
    {
    case ARG_N_COLUMNS: /* construct-only arg, only set when !GTK_CONSTRUCTED */
      if (ctree->tree_column)
        gtk_ctree_construct (ctree,
                             MAX (1, GTK_VALUE_UINT (*arg)),
                             ctree->tree_column, NULL);
      else
        GTK_CLIST (ctree)->columns = MAX (1, GTK_VALUE_UINT (*arg));
      break;
[...]
static void
gtk_ctree_get_arg (GtkObject      *object,
                   GtkArg         *arg,
                   guint           arg_id)
{
  GtkCTree *ctree;

  ctree = GTK_CTREE (object);

  switch (arg_id)
    {
    case ARG_N_COLUMNS:
      GTK_VALUE_UINT (*arg) = GTK_CLIST (ctree)->columns;
      break;

becomes:

static void
gtk_ctree_set_param (GObject        *object,
                     guint           param_id,
                     const GValue   *value,
                     GParamSpec     *pspec,
                     const gchar    *trailer)
{
  /* param_id is similar to arg_id, e.g. ARG_N_COLUMNS
   * pspec is the one from g_param_spec_uint() in class_init(),
   * value contains G_TYPE_UINT and the actuall integer.
   * trailer is a rarely used field, we can ignor that here
   */
  GtkCTree *ctree = GTK_CTREE (object);
  
  switch (param_id)
    {
    case ARG_N_COLUMNS: /* construct-only param */
      GTK_CLIST (ctree)->columns = g_value_get_uint (value);
      break;
    case ARG_TREE_COLUMN: /* construct-only param */
      ctree->tree_column = g_value_get_uint (value);
      break;
[...]
static void
gtk_ctree_get_param (GObject        *object,
                     guint           param_id,
                     GValue         *value,
                     GParamSpec     *pspec,
                     const gchar    *trailer)
{
  GtkCTree *ctree = GTK_CTREE (object);

  switch (param_id)
    {
    case ARG_N_COLUMNS:
      g_value_set_uint (value, GTK_CLIST (ctree)->columns);
      break;
                     

so far so good, that's basically it for widget implementators
as far as parameters are concerned (Glade or language binding
type of guys will want to learn more about g_param_spec_* and
g_value_* functions though). note, that Gtk+ 2.0 will contain
apropriate compatibility code to maintain the set_arg/get_arg/
gtk_object_add_arg_type interface for easy transition to
parameters, except for CONSTRUCT arguments (of which there
are not many out there in current code, and that has to be
fixed! ;)

now, when/where to do the actuall construction, based on the
GTK_CLIST (ctree)->columns and ctree->tree_column values set
from g_object_new() through gtk_ctree_get_param()?

to solve that, i'm going to basically virtualize g_object_new()
functionality, presumably by doing:

struct _GObjectConstructParam
{
  GParamSpec *pspec;
  guint       param_id;
  GValue     *value;
  gchar      *trailer;
};

struct  _GObjectClass
{
  [...]
  GObject*   (*constructor)     (GType                  type,
                                 guint                  n_construct_params,
                                 GObjectConstructParam *construct_params);
  [...]
};

the GObject default implementation of constructor() will simply
create an unconstructed instance through the type system, and
then call set_param() for all elements of construct_params().
that way, the ctree implementation simply has to become:

static void
gtk_ctree_class_init (GtkCTreeClass *klass)
{
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
  [...]
  gobject_class->constructor = gtk_ctree_constructor;
  [...]
}

static GObject*
gtk_ctree_constructor (GType                  type,
                       guint                  n_construct_params,
                       GObjectConstructParam *construct_params)
{
  GObject *object = G_OBJECT_CLASS (parent_class)->constructor (type,
                                                                n_construct_params,
                                                                construct_params);
  
  gtk_ctree_construct (GTK_CTREE (object),
                       GTK_CLIST (object)->columns,
                       GTK_CTREE (object)->tree_column,
                       NULL);

  return object;
}

to fully construct ctree internals according to the constructor
arguments given.

note, that gtk_ctree_construct() should actually become a static function,
since there's not going to be a way to create unconstructed objects anymore
that gtk_ctree_construct() could be invoked on.


as promised above, this also solves generic creation of singletons in a
satisfactory way, one'd simply do:

static MySingleton *the_singleton = NULL;

static GObject*
my_singleton_constructor (GType                  type,
                          guint                  n_construct_params,
                          GObjectConstructParam *construct_params)
{
  GObject *object;
  
  if (!the_singleton)
    {
      object = G_OBJECT_CLASS (parent_class)->constructor (type,
                                                           n_construct_params,
                                                           construct_params);
      the_singleton = MY_SINGLETON (object);
    }
  else
    object = g_object_ref (G_OBJECT (the_singleton));

  return object;
}

provided that the_singleton is zeroed out at finalization time. if the
singleton should persist untill the program exits, one would do:

-      the_singleton = MY_SINGLETON (object);
+      the_singleton = MY_SINGLETON (g_object_ref (object));


for mere mortals, i should probably note, that GObjectClass.constructor()
is only interesting if they a) require their objects to have constructor
arguments, or b) want to implement singeltons. (yeah, cruel me to only
mention this in line 372 of the email ;)

as far as gtk_type_new() is concerned, for future gtk versions, i'll
probably deprecate it and make it return constructed objects, the whole
GTK_CONSTRUCTED cruft can then vanish in Gtk+ (YES!). people that get
bitten by this will have to adapt their construction code anyways.
people that get not bitten by this should probably introduce construct
args to their objects/widgets if they take arguments in their _new()
functions.
as far as g_type_create_instance() is concerned, that's a private
function for implementators of fundamental types, and as long as
i'm the only one implementing fundamental types, i reserve the right
to rename it from g_type_create_instance() to g_type_rambokidize()
to g_type_owen_is_god() to _g_type_create_instance_dum_di_dum() on a
daily basis, so it won't get used in places it shouldn't be.

---
ciaoTJ






[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]