GTK+ / Gnome Application Development | |||
---|---|---|---|
<<< Previous | Home | Next >>> |
To write a GtkObject, you must implement the methods provided by the GtkObject interface, or at least be sure you are happy with the default implementations. There are only five GtkObject methods; two of them are get_arg and set_arg, described in the section called Using Object Arguments in Your Own GtkObject Subclass. The other three implement object destruction; here are the fields in GtkObjectClass:
void (* shutdown) (GtkObject *object); void (* destroy) (GtkObject *object); void (* finalize) (GtkObject *object); |
As you might guess from this, objects are destroyed in a three-stage process. Each method represents one stage in the process; if your object subclass overrides any of them, it must "chain up" to the corresponding method in the parent class (see the section called Chaining Up). The three methods do the following:
The shutdown method allows objects to perform actions before destruction begins. Most subclasses do not override this method; the default shutdown method emits the "destroy" signal to start the next phase. (The default implementation will always be invoked, even if overridden, because subclasses are required to "chain up.")
The destroy method marks the object "useless" and cleans up associated resources, but does not free the object itself. Typically a destroy method would free data, strings, and so on stored in the instance struct, and set the struct members to NULL. This is the method most subclasses override.
The finalize method is invoked only when the object's reference count reaches 0. The default implementation frees the object instance struct, so that further attempts to use the object result in a segmentation fault. The finalize method must also consider that user code could have been invoked after the destroy method, and free any data that user code could have allocated.
Note: Objects can be destroyed regardless of their reference count. This means that the shutdown method is invoked and the destroy signal is emitted. However, as long as the reference count is greater than 0, the object will not be finalized.
The shutdown method has no defined role; its purpose depends on the particular object. For example, the GtkWidget shutdown implementation removes the widget from its parent container, and unrealizes the widget. This is especially important for containers: their destroy method destroys all children of the container. If the container was not unrealized before destruction, it would still be visible and the user would see each child disappear, followed by the container. With the shutdown method, everything disappears at once.
The destroy method frees as many resources as possible without rendering the object "unsafe." If your object has invariants describing its integrity, a destroy method will not violate them. All public functions exported by an object implementation should gracefully handle destroyed objects (they should not crash---remember that an object can be destroyed while references to it persist). The finalize method actually frees the object, meaning that attempts to use the object become dangerous bugs.
The statement that "public functions exported by an object implementation should gracefully handle destroyed objects" requires some qualification. This is the intended behavior; otherwise, code could not ensure the sanity of an object by increasing its reference count. However, the implementation does not yet live up to the guarantee in all cases. Some public functions in GTK+ and Gnome still assume data structures freed in the destroy method exist, or re-allocate data structures the destroy method already freed. Unless the finalize method re-frees those data structures, they will be leaked. To avoid these bugs, it is best to avoid calling functions on destroyed objects (in practice, it would be uncommon to do so).
You can count on being able to check the type and object flags of a destroyed object, however; and it is certainly safe to call gtk_object_unref() on a destroyed object. In your own object implementations, be sure you implement each public function correctly; check whether the object is destroyed with GTK_OBJECT_DESTROYED(), and keep in mind that user code can run between the destroy method and the finalize method.
Notice that the destroy method is the default handler for a "destroy" signal, but the shutdown and finalize methods are class functions only. This reduces the complexity and increases the speed of the finalization process. Also, because finalize destroys the integrity of an object, it would be unsafe to emit as a signal (GTK+ does have a facility called "weak references" that allows you to invoke a callback when an object is finalized---weak references do not assume that the GtkObject is in a sane state).
To make things more concrete, let's look at the functions you would use to destroy an object. First, gtk_object_destroy():
void gtk_object_destroy (GtkObject *object) { g_return_if_fail (object != NULL); g_return_if_fail (GTK_IS_OBJECT (object)); g_return_if_fail (GTK_OBJECT_CONSTRUCTED (object)); if (!GTK_OBJECT_DESTROYED (object)) { gtk_object_ref (object); object->klass->shutdown (object); gtk_object_unref (object); } } |
Notice that destroyed-but-not-finalized objects are flagged, and this flag can be checked with the GTK_OBJECT_DESTROYED() macro. gtk_object_destroy() ensures that objects are not destroyed twice by ignoring any already-destroyed objects. If an object has not been destroyed, gtk_object_destroy() references it to prevent finalization during the destroy process and invokes the shutdown method; by default, that method looks like this:
static void gtk_object_shutdown (GtkObject *object) { GTK_OBJECT_SET_FLAGS (object, GTK_DESTROYED); gtk_signal_emit (object, object_signals[DESTROY]); } |
This method sets the destroyed flag, to ensure that any recursive gtk_object_destroy() calls have no effect; then it emits the "destroy" signal. gtk_object_shutdown() seems pointless by itself; however, subclasses may override this method with something more substantial, chaining up to the GtkObject default method (see the section called Chaining Up).
It may be unclear that gtk_object_shutdown() is a method implementation, while gtk_object_destroy() is a public function. Note that gtk_object_shutdown() is the internal function that implements the shutdown method for the GtkObject class, while gtk_object_destroy() is part of the public API. The GtkObject implementation of the destroy method is called gtk_object_real_destroy():
static void gtk_object_real_destroy (GtkObject *object) { if (GTK_OBJECT_CONNECTED (object)) gtk_signal_handlers_destroy (object); } |
This code simply cleans up any signal handlers associated with the object. gtk_object_real_destroy() is the default handler invoked when the "destroy" signal is emitted. gtk_object_destroy() invokes the (possibly overridden) class function shutdown; the default shutdown method emits the "destroy" signal.
Finalization is initiated by gtk_object_unref(), if and only if the reference count has reached 0. gtk_object_unref() can be invoked directly by a user, but often gtk_object_destroy() invokes it. Here it is:
void gtk_object_unref (GtkObject *object) { g_return_if_fail (object != NULL); g_return_if_fail (GTK_IS_OBJECT (object)); g_return_if_fail (object->ref_count > 0); if (object->ref_count == 1) { gtk_object_destroy (object); g_return_if_fail (object->ref_count > 0); } object->ref_count -= 1; if (object->ref_count == 0) { object->klass->finalize (object); } } |
If an object has a reference count of 1, calling gtk_object_unref() invokes the shutdown and destroy methods (via gtk_object_destroy()) and then finalizes the object (unless the reference count was incremented sometime during the shutdown/destroy process; this is allowed and will prevent finalization). If an object's reference count is greater than 1 at the start of gtk_object_unref(), the reference count is simply decremented.
Again, notice that an object can be destroyed while the reference count is greater than 1 if the user calls gtk_object_destroy(); if this happens, finalization does not take place until the holders of the remaining references call gtk_object_unref(). In the most common case, the gtk_object_destroy() implementation holds the last reference count --- have another look at the gtk_object_destroy() code with this in mind.
For completeness, here is GtkObject's default finalize method:
static void gtk_object_finalize (GtkObject *object) { gtk_object_notify_weaks (object); g_datalist_clear (&object->object_data); gtk_type_free (GTK_OBJECT_TYPE (object), object); } |
The three function calls in this method do the following:
Invoke "weak references," which are callbacks invoked on object finalization. This is a little-used GtkObject feature not described in this book (usually connecting to the "destroy" signal is more appropriate).
Clear any object data (described in the section called Attaching Data to Objects).
Free the instance struct.
the section called Widget Life Cycle in the chapter called GTK+ Basics has more to say about reference counting and destruction with respect to widgets.
If an object overrides the shutdown, destroy, or finalize methods, it should chain up to the default implementation, to ensure that each parent class has a chance to clean up. Here is an example of chaining up:
static void gtk_widget_real_destroy (GtkObject *object) { /* ... */ if (parent_class->destroy) parent_class->destroy (object); }; |
gtk_widget_real_destroy() is installed in the widget's class struct in the class initialization function, overwriting the GtkObject default. parent_class is a pointer to the parent's class struct; usually you will want to store this pointer in your class initialization function, as GtkWidget does:
static GtkObjectClass *parent_class = NULL; /* ... code omitted ... */ static void gtk_widget_class_init (GtkWidgetClass *klass) { GtkObjectClass *object_class; object_class = (GtkObjectClass*) klass; parent_class = gtk_type_class (gtk_object_get_type ()); /* ... code omitted ... */ object_class->set_arg = gtk_widget_set_arg; object_class->get_arg = gtk_widget_get_arg; object_class->shutdown = gtk_widget_shutdown; object_class->destroy = gtk_widget_real_destroy; object_class->finalize = gtk_widget_finalize; } |
Of course, if parent_class is not a GtkObjectClass*, you will need to cast it with the GTK_OBJECT_CLASS() macro.
An aside: notice that you should not chain up when implementing get_arg and set_arg --- GTK+ special-cases these methods in gtk_object_set() and gtk_object_get(). Recall that the GtkObject base class initializer zeroes these two methods, rather than leaving the default implementation. When setting or getting an argument value, GTK+ uses the information provided on argument registration to jump directly to the correct class struct and invoke only the correct get_arg or set_arg method. Chaining up would be a much slower way to implement the same thing (and would require unique argument IDs within the same class ancestry).