6/06/2011

GObject 入門 (Part 4) - 物件的建構與解構

基本上所有屬於GObject之物件均配置於堆疊(heap)上,加之GObject system本身並無記憶體垃圾回收(memory garbage collection)機制,所以物件的管理就要稍微留心,不然可能會產生記憶體洩漏(memory leakage),甚至資源洩漏(resource leakage)的危機。

參考計數器(reference counter)
為了追蹤物件的生命週期以有效地進行記憶體管理(memory management),GObject引用一套參考計數器(reference counter)達成上述目的。它的運作原理並不複雜,當每個GObject物件產生後(透過g_object_new),該物件之參考計數(reference counter)為1,之後物件就可不斷被呼叫、操作,當物件之參考計數(reference count)歸零時,GObject system將立即解構(destruction)該物件。在這期間,使用該物件者可透過g_object_ref增加該物件的參考數(reference counter),以防該物件被突然解構;反之也可以由g_object_unref解除對該物件的參考。所有GNOME下運用到GObject的函式庫,例如GTK+、GStreamer等都大量運用這種方式進行物件管理。
在上一篇的例子裡,就有這樣的使用範例。
int main(int argc, char** argv)
{
    Rectangle* rect;
    Circle* circle;
 
    g_type_init();
 
    rect = rectangle_new(4, 5);
    g_print("Area of Rectangle(0x%08x) is %u.\n",
            (guint)rect, shape_calculate_area(SHAPE(rect)));
    rectangle_free(rect);
 
    circle = circle_new(6);
    g_print("Area of Circle(0x%08x) is %u.\n",
            (guint)circle, shape_calculate_area(SHAPE(circle)));
    circle_free(circle);
 
    return 0;
}
... ... ... ...

void rectangle_free(Rectangle* rect)
{
    g_assert(rect != NULL);
    g_return_if_fail(IS_RECTANGLE(rect));
 
    g_object_unref(G_OBJECT(rect));
}

    ... ... ... ...

void circle_free(Circle* circle)
{
    g_assert(circle != NULL);
    g_return_if_fail(IS_CIRCLE(circle));
 
    g_object_unref(G_OBJECT(circle));
}

    ... ... ... ...
其中,rectangle_free()與circle_free()都包裝了g_object_unref(),在本例中這僅僅是吾人為了符合C programming language的使用慣例。如果去深入追蹤GTK或是GStreamer的原始碼,可以發現程式碼中對g_object_unref()之直接呼叫。若今日系統欲搭配其他函式庫可以試著變更如下,以配合其他函式庫的使用慣例。
int main(int argc, char** argv)
{
    Rectangle* rect;
    Circle* circle;
 
    g_type_init();
 
    rect = rectangle_new(4, 5);
    g_print("Area of Rectangle(0x%08x) is %u.\n",
            (guint)rect, shape_calculate_area(SHAPE(rect)));
    g_object_unref(G_OBJECT(rect));
 
    circle = circle_new(6);
    g_print("Area of Circle(0x%08x) is %u.\n",
            (guint)circle, shape_calculate_area(SHAPE(circle)));
    g_object_unref(G_OBJECT(circle));

    return 0;
}
值得一提的是,GObject system的實作方式有時會讓C programming language編譯時期(compilation time)的型別檢定(type checking)無法運作,導致編譯器(compiler)抱怨連連,你可以選擇忽略或是強制轉型(type casting)以滿足編譯器的型別檢查機制。

GObject物件之建構
當屬於物件的記憶體被配置後,GInstanceInit()會立即被呼叫以進行初始化(initialization),接著建構子(constructor)會喚起,至此物件的建構就大致完成了。以下節錄自官方文件的說明。


Once g_object_new has obtained a reference to an initialized class structure, it invokes its constructor method to create an instance of the new object. Since it has just been overridden by maman_bar_class_init tomaman_bar_constructor, the latter is called and, because it was implemented correctly, it chains up to its parent's constructor. In order to find the parent class and chain up to the parent class constructor, we can use the maman_bar_parent_class pointer that has been set up for us by the G_DEFINE_TYPE macro.
Finally, at one point or another, g_object_constructor is invoked by the last constructor in the chain. This function allocates the object's instance' buffer through g_type_create_instance which means that the instance_init function is invoked at this point if one was registered. After instance_init returns, the object is fully initialized and should be ready to answer any user-request. When g_type_create_instance returns,g_object_constructor sets the construction properties (i.e. the properties which were given tog_object_new) and returns to the user's constructor which is then allowed to do useful instance initialization...
GObject物件之解構
當物件的參考計數(reference counter)歸零後,GObject會將解構(destruction)。在概念上,解構流程與物件的建構流程相反,解構子(destructor)會先被喚起,最後將記憶體歸還堆疊(heap)。話雖如此,在GObject system中,其實並無明確的解構子(destructor)與建構子(constructor)對應,GObject將解構流程拆分為dispose與finalize兩個動作。如果物件成員(object's member)有參考到其他物件,在dispose階段,要記得解除對該物件的參考( 說白了就是呼叫g_object_unref);在finalize階段可以釋放針對物件配置的資源。

... the first phase, executed in the dispose handler is supposed to release all references to other member objects. The second phase, executed by the finalize handler is supposed to complete the object's destruction process. Object methods should be able to run without program error (that is, without segfault :) in-between the two phases.
關於上面的敘述,直接參考官方文件上的範例就可一目了然。
... ... ... ...

static void maman_bar_dispose(GObject *gobject)
{
  MamanBar *self = MAMAN_BAR (gobject);

  /* 
   * 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.
   */
  if (self->priv->an_object)
    {
      g_object_unref (self->priv->an_object);

      self->priv->an_object = NULL;
    }

  /* Chain up to the parent class */
  G_OBJECT_CLASS (maman_bar_parent_class)->dispose (gobject);
}

static void maman_bar_finalize(GObject *gobject)
{
  MamanBar *self = MAMAN_BAR (gobject);

  g_free (self->priv->a_string);

  /* Chain up to the parent class */
  G_OBJECT_CLASS (maman_bar_parent_class)->finalize (gobject);
}

    ... ... ... ...
綜合以上內容,試著將Shape、Rectangle與Circle之實作補充得更完整。在此列舉Circle之內容。
... ... ... ...

static gpointer circle_parent_class = NULL;

... ... ... ...

static void circle_class_init(CircleClass* klass)
{
    g_print("Circle(0x%08x)::class::initialize\n", (guint)klass);

    circle_parent_class = g_type_class_peek_parent(klass);

    G_OBJECT_CLASS(klass)->constructor = circle_constructor;
    G_OBJECT_CLASS(klass)->dispose     = circle_dispose;
    G_OBJECT_CLASS(klass)->finalize    = circle_finalize;

    SHAPE_CLASS(klass)->calculateArea = (void*)circle_calculate_area;

    g_type_class_add_private(klass, sizeof(CirclePrivate));
}

static GObject* circle_constructor(GType                  gtype,
                                   guint                  n_properties,
                                   GObjectConstructParam* properties)
{
    GObject* object;

    {
        /* Always chain up to the parent constructor */
        object = G_OBJECT_CLASS(circle_parent_class)->constructor(gtype,
                                                                  n_properties,
                                                                  properties);
    }

    /* update the object state depending on constructor properties */

    g_print("Circle(0x%08x)::constructor\n", (guint)object);

    return object;
}

static void circle_dispose(GObject* object)
{
    g_print("Circle(0x%08x)::dispose\n", (guint)object);

    /* Chain up to the parent class */
    G_OBJECT_CLASS(circle_parent_class)->dispose(object);
}

static void circle_finalize(GObject* object)
{
    g_print("Circle(0x%08x)::finalize\n", (guint)object);

    /* Chain up to the parent class */
    G_OBJECT_CLASS(circle_parent_class)->finalize(object);
}

    ... ... ... ...
執行結果:

原始碼下載

No comments:

Post a Comment