[glib/wip/private-rework-3: 3/4] docs: Clean up the GObject tutorial a bit
- From: Emmanuele Bassi <ebassi src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [glib/wip/private-rework-3: 3/4] docs: Clean up the GObject tutorial a bit
- Date: Tue, 18 Jun 2013 15:18:45 +0000 (UTC)
commit 32dc065af0119632f76bb10f0f4308c03c6c25a6
Author: Emmanuele Bassi <ebassi gnome org>
Date: Wed Jun 12 15:18:29 2013 +0100
docs: Clean up the GObject tutorial a bit
Started off by using the new instance private data macro, ended up
cleaning up the obscure, out of date, or simply broken concepts and
paragraphs.
https://bugzilla.gnome.org/show_bug.cgi?id=700035
docs/reference/gobject/tut_howto.xml | 303 ++++++++++++++++------------------
1 files changed, 142 insertions(+), 161 deletions(-)
---
diff --git a/docs/reference/gobject/tut_howto.xml b/docs/reference/gobject/tut_howto.xml
index 1ee0051..436c636 100644
--- a/docs/reference/gobject/tut_howto.xml
+++ b/docs/reference/gobject/tut_howto.xml
@@ -40,8 +40,14 @@
grumpy (not a good thing)</para></listitem>
<listitem><para>You must assess the fact that these conventions might
have been designed by both smart and experienced people: maybe they
- were at least partly right. Try to put your ego aside.</para></listitem>
+ were at least partly right. Try to put your ego aside.</para></listitem>
</itemizedlist>
+ It is, nevertheless, important to note that these rules generally apply
+ to code that is meant to be called by third parties; it is perfectly
+ possible to write a valid, self-contained GObject types without most of
+ the boilerplate used in this tutorial; most of the boilerplate is also
+ not strictly required if you plan to use the GObject types only through
+ language bindings based on introspection.
</para>
<para>
@@ -111,6 +117,7 @@ typedef struct _MamanBarClass MamanBarClass;
struct _MamanBar
{
+ /* Parent instance structure */
GObject parent_instance;
/* instance members */
@@ -118,6 +125,7 @@ struct _MamanBar
struct _MamanBarClass
{
+ /* Parent class structure */
GObjectClass parent_class;
/* class members */
@@ -134,51 +142,47 @@ GType maman_bar_get_type (void);
</programlisting>
</para></listitem>
<listitem><para>
- Most GTK+ types declare their private fields in the public header
- with a /* private */ comment, relying on their user's intelligence
- not to try to play with these fields. Fields not marked private
- are considered public by default. The /* protected */ comment
- (same semantics as those of C++) is also used, mainly in the GType
- library, in code written by Tim Janik.
+ Types that require per-instance private data should use the
+ G_DEFINE_TYPE_WITH_PRIVATE() macro, or use the G_ADD_PRIVATE()
+ macro with the G_DEFINE_TYPE_WITH_CODE() or the G_DEFINE_TYPE_EXTENDED()
+ macros. The private structure is then defined in the .c file,
+ and can be accessed using the <function>get_private()</function>
+ function generated by the G_DEFINE_TYPE_* macros.
<programlisting>
-struct _MamanBar
+struct _MamanBarPrivate
{
- GObject parent_instance;
-
- /*< private >*/
int hsize;
};
-</programlisting>
- </para></listitem>
- <listitem><para>
- All of Nautilus code and a lot of GNOME libraries use private
- indirection members, as described by Herb Sutter in his Pimpl
- articles(see <ulink url="http://www.gotw.ca/gotw/024.htm">Compilation Firewalls</ulink>
- and <ulink url="http://www.gotw.ca/gotw/028.htm">The Fast Pimpl Idiom</ulink>:
- he summarizes the different issues better than I will).
-<programlisting>
-typedef struct _MamanBarPrivate MamanBarPrivate;
-struct _MamanBar
+G_DEFINE_TYPE_WITH_PRIVATE (MamanBar, maman_bar, G_TYPE_OBJECT)
+
+static void
+maman_bar_class_init (MamanBarClass *klass)
{
- GObject parent_instance;
-
- /*< private >*/
- MamanBarPrivate *priv;
-};
+}
+
+static void
+maman_bar_init (MamanBar *self)
+{
+ /* maman_bar_get_private() is generated by G_DEFINE_TYPE_WITH_PRIVATE()
+ * above, and it's local to the current compilation unit.
+ */
+ MamanBarPrivate *priv = maman_bar_get_private (self);
+
+ priv->hsize = 42;
+}
</programlisting>
- <note><simpara>Do not call this <varname>private</varname>, as
- that is a registered c++ keyword.</simpara></note>
-
- The private structure is then defined in the .c file, using the
- g_type_class_add_private() function to notify the presence of
- a private memory area for each instance and it can either
- be retrieved using <function>G_TYPE_INSTANCE_GET_PRIVATE()</function>
- each time is needed, or assigned to the <literal>priv</literal>
- member of the instance structure inside the object's
- <function>init</function> function.
+ </para></listitem>
+
+ <listitem><para>
+ Most GNOME libraries use a pointer inside the instance structure
+ for simpler access to the private data structure, as described by
+ Herb Sutter in his Pimpl article (see <ulink url="http://www.gotw.ca/gotw/024.htm">Compilation
Firewalls</ulink>
+ and <ulink url="http://www.gotw.ca/gotw/028.htm">The Fast Pimpl Idiom</ulink>
+ for reference). If you opt to use this idiom, you can assign the
+ pointer inside the instance initialization function, e.g.:
<programlisting>
-#define MAMAN_BAR_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), MAMAN_TYPE_BAR, MamanBarPrivate))
+G_DEFINE_TYPE_WITH_PRIVATE (MamanBar, maman_bar, G_TYPE_OBJECT)
struct _MamanBarPrivate
{
@@ -188,27 +192,14 @@ struct _MamanBarPrivate
static void
maman_bar_class_init (MamanBarClass *klass)
{
- g_type_class_add_private (klass, sizeof (MamanBarPrivate));
}
static void
maman_bar_init (MamanBar *self)
{
- MamanBarPrivate *priv;
-
- self->priv = priv = MAMAN_BAR_GET_PRIVATE (self);
-
- priv->hsize = 42;
+ self->priv = maman_bar_get_private (self);
+ self->priv->hsize = 42;
}
-</programlisting>
- </para></listitem>
-
- <listitem><para>
- You don't need to free or allocate the private structure, only the
- objects or pointers that it may contain. Another advantage of this
- to the previous version is that is lessens memory fragmentation,
- as the public and private parts of the instance memory are
- allocated at once.
</para></listitem>
</itemizedlist>
</para>
@@ -280,7 +271,7 @@ struct _MamanBarPrivate {
</itemizedlist>
<programlisting>
-G_DEFINE_TYPE (MamanBar, maman_bar, G_TYPE_OBJECT);
+G_DEFINE_TYPE (MamanBar, maman_bar, G_TYPE_OBJECT)
</programlisting>
</para>
@@ -289,7 +280,9 @@ G_DEFINE_TYPE (MamanBar, maman_bar, G_TYPE_OBJECT);
<function>G_DEFINE_TYPE_WITH_CODE</function> macro to control the
get_type function implementation - for instance, to add a call to
<function>G_IMPLEMENT_INTERFACE</function> macro which will
- call the <function>g_type_implement_interface</function> function.
+ call the <function>g_type_implement_interface</function> function,
+ or call the <function>G_ADD_PRIVATE</function> macro will add an
+ instance private data structure.
</para>
</sect1>
@@ -319,28 +312,36 @@ G_DEFINE_TYPE (MamanBar, maman_bar, G_TYPE_OBJECT);
</para>
<para>
+ It is important to note that object construction cannot <emphasis>ever</emphasis>
+ fail. If you require a fallible GObject construction, you can use the
+ GInitable and GAsyncInitable interfaces provided by the GIO library
+ </para>
+
+ <para>
As such, I would recommend writing the following code first:
<programlisting>
+G_DEFINE_TYPE_WITH_PRIVATE (MamanBar, maman_bar, G_TYPE_OBJECT)
+
+static void
+maman_bar_class_init (MamanBarClass *klass)
+{
+}
+
static void
maman_bar_init (MamanBar *self)
{
- self->priv = MAMAN_BAR_GET_PRIVATE (self);
+ self->priv = maman_bar_get_private (self);
/* initialize all public and private members to reasonable default values. */
-
- /* If you need specific construction properties to complete initialization,
- * delay initialization completion until the property is set.
- */
}
</programlisting>
</para>
<para>
- Now, if you need special construction properties, install the properties in the class_init function,
- override the set and get methods and implement the get and set methods as described in
- <xref linkend="gobject-properties"/>. Make sure that these properties use a construct only
- <link linkend="GParamSpec"><type>GParamSpec</type></link> by setting the param spec's flag field to
G_PARAM_CONSTRUCT_ONLY: this helps
- GType ensure that these properties are not set again later by malicious user code.
+ If you need special construction properties, install the properties in
+ the <function>class_init()</function> function, override the <function>set_property()</function>
+ and <function>get_property()</function> methods of the GObject class,
+ and implement them as described by <xref linkend="gobject-properties"/>.
<informalexample><programlisting>
enum {
PROP_0,
@@ -350,6 +351,7 @@ enum {
N_PROPERTIES
};
+/* Keep a pointer to the properties definition */
static GParamSpec *obj_properties[N_PROPERTIES] = { NULL, };
static void
@@ -365,32 +367,31 @@ bar_class_init (MamanBarClass *klass)
"Maman construct prop",
"Set maman's name",
"no-name-set" /* default value */,
- G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE);
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (gobject_class,
N_PROPERTIES,
obj_properties);
}
</programlisting></informalexample>
- If you need this, make sure you can build and run code similar to the code shown above. Make sure
- your construct properties can set correctly during construction, make sure you cannot set them
- afterwards and make sure that if your users do not call <function><link
linkend="g-object-new">g_object_new</link></function>
- with the required construction properties, these will be initialized with the default values.
- </para>
-
- <para>
- I consider good taste to halt program execution if a construction property is set its
- default value. This allows you to catch client code which does not give a reasonable
- value to the construction properties. Of course, you are free to disagree but you
- should have a good reason to do so.
+ If you need this, make sure you can build and run code similar to the
+ code shown above. Also, make sure your construct properties can be set
+ without side effects during construction.
</para>
<para>
- Some people sometimes need to construct their object but only after
- the construction properties have been set. This is possible through
- the use of the constructor class method as described in
- <xref linkend="gobject-instantiation"/> or, more simply, using
- the constructed class method available since GLib 2.12.
+ Some people sometimes need to complete the initialization of a instance
+ of a type only after the properties passed to the constructors have been
+ set. This is possible through the use of the <function>constructor()</function>
+ class method as described in <xref linkend="gobject-instantiation"/> or,
+ more simply, using the <function>constructed()</function> class method
+ available since GLib 2.12. Note that the <function>constructed()</function>
+ virtual function will only be invoked after the properties marked as
+ G_PARAM_CONSTRUCT_ONLY or G_PARAM_CONSTRUCT have been consumed, but
+ before the regular properties passed to <function>g_object_new()</function>
+ have been set.
</para>
</sect1>
@@ -407,10 +408,11 @@ bar_class_init (MamanBarClass *klass)
<para>
The destruction process of your object might be split in two different
- phases: dispose and the finalize.
+ phases: dispose and the finalize. This split is necessary to handle
+ potential cycles due to the nature of the reference counting mechanism
+ used by GObject, as well as dealing with temporary vivification of
+ instances in case of signal emission during the destruction sequence.
<programlisting>
-#define MAMAN_BAR_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), MAMAN_TYPE_BAR, MamanBarPrivate))
-
struct _MamanBarPrivate
{
GObject *an_object;
@@ -418,31 +420,29 @@ struct _MamanBarPrivate
gchar *a_string;
};
-G_DEFINE_TYPE (MamanBar, maman_bar, G_TYPE_OBJECT);
+G_DEFINE_TYPE_WITH_PRIVATE (MamanBar, maman_bar, G_TYPE_OBJECT)
static void
maman_bar_dispose (GObject *gobject)
{
MamanBar *self = MAMAN_BAR (gobject);
- /*
- * In dispose, you are supposed to free all types referenced from this
+ /* In dispose(), you are supposed to free all types referenced from this
* object which might themselves hold a reference to self. Generally,
* the most simple solution is to unref all members on which you own a
* reference.
*/
- /* dispose might be called multiple times, so we must guard against
- * calling g_object_unref() on an invalid GObject.
+ /* dispose() might be called multiple times, so we must guard against
+ * calling g_object_unref() on an invalid GObject by setting the member
+ * NULL; g_clear_object() does this for us, atomically.
*/
- if (self->priv->an_object)
- {
- g_object_unref (self->priv->an_object);
-
- self->priv->an_object = NULL;
- }
+ g_clear_object (&self->priv->an_object);
- /* Chain up to the parent class */
+ /* Always chain up to the parent class; there is no need to check if
+ * the parent class implements the dispose() virtual function: it is
+ * always guaranteed to do so
+ */
G_OBJECT_CLASS (maman_bar_parent_class)->dispose (gobject);
}
@@ -453,7 +453,9 @@ maman_bar_finalize (GObject *gobject)
g_free (self->priv->a_string);
- /* Chain up to the parent class */
+ /* Always chain up to the parent class; as with dispose(), finalize()
+ * is guaranteed to exist on the parent's class virtual function table
+ */
G_OBJECT_CLASS (maman_bar_parent_class)->finalize (gobject);
}
@@ -464,14 +466,12 @@ maman_bar_class_init (MamanBarClass *klass)
gobject_class->dispose = maman_bar_dispose;
gobject_class->finalize = maman_bar_finalize;
-
- g_type_class_add_private (klass, sizeof (MamanBarPrivate));
}
static void
maman_bar_init (MamanBar *self);
{
- self->priv = MAMAN_BAR_GET_PRIVATE (self);
+ self->priv = maman_bar_get_private (self);
self->priv->an_object = g_object_new (MAMAN_TYPE_BAZ, NULL);
self->priv->a_string = g_strdup ("Maman");
@@ -480,15 +480,10 @@ maman_bar_init (MamanBar *self);
</para>
<para>
- Add similar code to your GObject, make sure the code still builds
- and runs: dispose and finalize must be called during the last unref.
- </para>
-
- <para>
It is possible that object methods might be invoked after dispose is
run and before finalize runs. GObject does not consider this to be a
program error: you must gracefully detect this and neither crash nor
- warn the user.
+ warn the user, by having a disposed instance revert to an inhert state.
</para>
</sect1>
@@ -498,7 +493,7 @@ maman_bar_init (MamanBar *self);
<para>
Just as with C++, there are many different ways to define object
methods and extend them: the following list and sections draw on
- C++ vocabulary. (Readers are expected to know basic C++ buzzwords.
+ C++ vocabulary. (Readers are expected to know basic C++ concepts.
Those who have not had to write C++ code recently can refer to e.g.
<ulink url="http://www.cplusplus.com/doc/tutorial/"/> to refresh
their memories.)
@@ -537,8 +532,6 @@ maman_bar_do_action (MamanBar *self, /* parameters */)
}
</programlisting>
</para>
-
- <para>There is really nothing scary about this.</para>
</sect2>
<sect2>
@@ -572,28 +565,7 @@ maman_bar_do_action (MamanBar *self, /* parameters */)
}
</programlisting>
The code above simply redirects the do_action call to the relevant
- class function. Some users, concerned about performance, do not
- provide the <function>maman_bar_do_action</function> wrapper function
- and require users to dereference the class pointer themselves. This
- is not such a great idea in terms of encapsulation and makes it
- difficult to change the object's implementation afterwards, should
- this be needed.
- </para>
-
- <para>
- Other users, also concerned by performance issues, declare
- the <function>maman_bar_do_action</function> function inline in the
- header file. This, however, makes it difficult to change the
- object's implementation later (although easier than requiring users
- to directly dereference the class function) and is often difficult
- to write in a portable way (the <emphasis>inline</emphasis> keyword
- is part of the C99 standard but not every compiler supports it).
- </para>
-
- <para>
- In doubt, unless a user shows you hard numbers about the performance
- cost of the function call, just implement <function>maman_bar_do_action</function>
- in the source file.
+ class function.
</para>
<para>
@@ -601,8 +573,8 @@ maman_bar_do_action (MamanBar *self, /* parameters */)
implementation for this class method in the object's
<function>class_init</function> function: initialize the
klass->do_action field to a pointer to the actual implementation.
- You can also make this class method pure virtual by initializing
- the klass->do_action field to NULL:
+ By default, class method that are not inherited are initialized to
+ NULL, and thus are to be considered "pure virtual".
<programlisting>
static void
maman_bar_real_do_action_two (MamanBar *self, /* parameters */)
@@ -613,7 +585,10 @@ maman_bar_real_do_action_two (MamanBar *self, /* parameters */)
static void
maman_bar_class_init (BarClass *klass)
{
- /* pure virtual method: mandates implementation in children. */
+ /* this is not necessary, except for demonstration purposes.
+ *
+ * pure virtual method: mandates implementation in children.
+ */
klass->do_action_one = NULL;
/* merely virtual method. */
@@ -625,7 +600,17 @@ maman_bar_do_action_one (MamanBar *self, /* parameters */)
{
g_return_if_fail (MAMAN_IS_BAR (self));
- MAMAN_BAR_GET_CLASS (self)->do_action_one (self, /* parameters */);
+ /* if the method is purely virtual, then it is a good idea to
+ * check that it has been overridden before calling it, and,
+ * depending on the intent of the class, either ignore it silently
+ * or warn the user.
+ /
+ if (MAMAN_BAR_GET_CLASS (self)->do_action_one != NULL)
+ MAMAN_BAR_GET_CLASS (self)->do_action_one (self, /* parameters */);
+ else
+ g_warning ("Class '%s' does not override the mandatory "
+ "MamanBarClass.do_action_one() virtual function.",
+ G_OBJECT_TYPE_NAME (self));
}
void
@@ -728,21 +713,16 @@ maman_bar_subtype_class_init (MamanBarSubTypeClass *klass)
<listitem><para>Child class B re-implements method <function>foo</function>.</para></listitem>
<listitem><para>In the method B::foo, the child class B calls its parent class method
A::foo.</para></listitem>
</itemizedlist>
- There are many uses to this idiom:
+ There are various uses to this idiom:
<itemizedlist>
- <listitem><para>You need to change the behaviour of a class without modifying its code. You create
+ <listitem><para>You need to extend the behaviour of a class without modifying its code. You create
a subclass to inherit its implementation, re-implement a public virtual method to modify the
behaviour
- slightly and chain up to ensure that the previous behaviour is not really modified, just extended.
+ and chain up to ensure that the previous behaviour is not really modified, just extended.
</para></listitem>
- <listitem><para>You are lazy, you have access to the source code of the parent class but you don't
want
- to modify it to add method calls to new specialized method calls: it is faster to hack the child
class
- to chain up than to modify the parent to call down.</para></listitem>
<listitem><para>You need to implement the Chain Of Responsibility pattern: each object of the
inheritance
tree chains up to its parent (typically, at the beginning or the end of the method) to ensure that
they each handler is run in turn.</para></listitem>
</itemizedlist>
- I am personally not really convinced any of the last two uses are really a good idea but since this
- programming idiom is often used, this section attempts to explain how to implement it.
</para>
<para>
@@ -763,22 +743,25 @@ maman_bar_subtype_class_init (MamanBarSubTypeClass *klass)
</footnote>
</para>
- <para>The function <function><link
linkend="g-type-class-peek-parent">g_type_class_peek_parent</link></function> is used to access the original
parent
- class structure. Its input is a pointer to the class of the derived object and it returns a pointer
- to the original parent class structure. The code below shows how you could use it:
+ <para>The function <function><link
linkend="g-type-class-peek-parent">g_type_class_peek_parent</link></function>
+ is used to access the original parent class structure. Its input is a
+ pointer to the class of the derived object and it returns a pointer to
+ the original parent class structure. Instead of using this function
+ directly, though, you should use the <function>parent_class</function>
+ pointer created and initialized for us by the G_DEFINE_TYPE_* family of
+ macros, for instance:
<programlisting>
static void
b_method_to_call (B *obj, int a)
{
- BClass *klass;
- AClass *parent_class;
-
- klass = B_GET_CLASS (obj);
- parent_class = g_type_class_peek_parent (klass);
-
/* do stuff before chain up */
- parent_class->method_to_call (obj, a);
+ /* call the method_to_call() virtual function on the
+ * parent of BClass, AClass.
+ *
+ * remember the explicit cast to AClass*
+ */
+ A_CLASS (b_parent_class)->method_to_call (obj, a);
/* do stuff after chain up */
}
@@ -963,7 +946,7 @@ static void maman_ibaz_interface_init (MamanIbazInterface *iface);
G_DEFINE_TYPE_WITH_CODE (MamanBar, maman_bar, G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (MAMAN_TYPE_IBAZ,
- maman_ibaz_interface_init));
+ maman_ibaz_interface_init))
</programlisting>
This definition is very much like all the similar functions we looked
at previously. The only interface-specific code present here is the call to
@@ -1095,7 +1078,7 @@ G_DEFINE_TYPE_WITH_CODE (MamanBar, maman_bar, G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (MAMAN_TYPE_IBAZ,
maman_ibaz_interface_init)
G_IMPLEMENT_INTERFACE (MAMAN_TYPE_IBAR,
- maman_ibar_interface_init));
+ maman_ibar_interface_init))
</programlisting>
It is very important to notice that the order in which interface
implementations are added to the main object is not random:
@@ -1326,18 +1309,16 @@ maman_derived_ibaz_interface_init (MamanIbazInterface *iface)
G_DEFINE_TYPE_WITH_CODE (MamanDerivedBaz, maman_derived_baz, MAMAN_TYPE_BAZ,
G_IMPLEMENT_INTERFACE (MAMAN_TYPE_IBAZ,
- maman_derived_ibaz_interface_init)
+ maman_derived_ibaz_interface_init))
static void
maman_derived_baz_class_init (MamanDerivedBazClass *klass)
{
-
}
static void
maman_derived_baz_init (MamanDerivedBaz *self)
{
-
}
</programlisting>
</para>
@@ -1357,8 +1338,8 @@ maman_derived_baz_init (MamanDerivedBaz *self)
exists)
<footnote>
<para>A Python callback can be connected to any signal on any
- C-based GObject.
- </para>
+ C-based GObject, and vice versa, assuming that the Python object
+ inherits from GObject.</para>
</footnote>
to any signal and to stop the emission of any signal at any
state of the signal emission process. This flexibility makes it
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]