# Object Construction

Tim Janik timj@gtk.org
Tue, 29 Aug 2000 05:14:46 +0200 (CEST)

- Previous message: Status of frame-buffer/nanox port?
- Next message: gdk-pixbuf-save
- Messages sorted by: [ date ] [ thread ] [ subject ] [ author ]

    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

- Previous message: Status of frame-buffer/nanox port?
- Next message: gdk-pixbuf-save
- Messages sorted by: [ date ] [ thread ] [ subject ] [ author ]