GObject对象系统(7) 定义和实现一个新的GObject

How to define and implement a new GObject

Boilerplate header code Boilerplate code Object construction Object destruction Object methods
Non-virtual public methods Virtual public methods Virtual private Methods
Chaining up

This chapter focuses on the implementation of a subtype of GObject, for example to create a custom class hierarchy, or to subclass a GTK+ widget.

本章节专注于讨论怎么实现一个GObject的子类型,例如,创建一个自定义的派生类,或者派生一个GTK+控件。


Throughout the chapter, a running example of a file viewer program is used, which has a ViewerFile class to represent a single file being viewed, and various derived classes for different types of files with special functionality, such as audio files. The example application also supports editing files (for example, to tweak a photo being viewed), using a ViewerEditable interface.

本章将使用一个文件阅读器作为例子,其中用VIewerFile类代表一个正在阅读的文件,对多个不同的文件类型派生多个子类来实现特定的功能,例如音频文件。本例子同样支持通过ViewerEditable接口来编辑文件。


Boilerplate header code 示例头文件代码

The first step before writing the code for your GObject is to write the type's header which contains the needed type, function and macro definitions. Each of these elements is nothing but a convention which is followed by almost all users of GObject, and has been refined over multiple years of experience developing GObject-based code. If you are writing a library, it is particularly important for you to adhere closely to these conventions; users of your library will assume that you have. Even if not writing a library, it will help other people who want to work on your project.

实现你的GObject的第一步是写一个包含所需类型、函数、宏定义的头文件。每一个这些元素都是几乎所有GObject开发者都遵守的的惯例,并经过了GObject开发者多年开发经验的改善。如果你在开发一个库,紧密地遵守这些惯例十分重要,你的库的用户也会假定你遵守了这些惯例。即使你没有在开发一个库,它也会帮助那些想在你的项目上工作的人。


Pick a name convention for your headers and source code and stick to it:

  • use a dash to separate the prefix from the typename: viewer-file.h and viewer-file.c (this is the convention used by Nautilus and most GNOME libraries).

  • use an underscore to separate the prefix from the typename: viewer_file.h and viewer_file.c.

  • Do not separate the prefix from the typename: viewerfile.h and viewerfile.c. (this is the convention used by GTK+)

为你的头文件和源代码文件选择一个名称惯例:
* 使用短线分隔前缀和类型名,如 viewer-file.h  和   viewer-file.c(这是Nautilus和大多数GNOME库使用的惯例);
* 使用下划线分隔前缀和类型名,如viewer_file.h 和 viewer_file.c;
* 不分隔前缀和类型名,如viewerfile.h 和 viewerfile.c(这是GTK+所使用的惯例)。

Some people like the first two solutions better: it makes reading file names easier for those with poor eyesight.

一些人更喜欢前两个,因为它让那些有视力障碍的人更易于阅读文件名。


The basic conventions for any header which exposes a GType are described in the section called “Conventions”.

If you want to declare a type named ‘file’ in namespace ‘viewer’, name the type instance ViewerFile and its classViewerFileClass (names are case sensitive). The recommended method of declaring a type differs based on whether the type is final or derivable.

如果你在命名空间"viewer"中声明一个名为"file"的类型,则实例类型为ViewerFile,类结构类型为ViewerFIleClass(大小写敏感)。声明一个类型的推荐方法取决于该类型是否是可继承的类型或不可继承的终类型。


Final types cannot be subclassed further, and should be the default choice for new types — changing a final type to be derivable is always a change that will be compatible with existing uses of the code, but the converse will often cause problems. Final types are declared using G_DECLARE_FINAL_TYPE, and require a structure to hold the instance data to be declared in the source code (not the header file).

终类型是在之后不能再被派生的类型,这应该是新类型的默认选择:将一个终类型转换为一个可继承的类型总是可以与当前的代码兼容的,但相反的转换却经常会出现问题。终类型由G_DECLARE_FINAL_TYPE来声明,并且需要在源文件中(而不是头文件)定义一个存储实例数据的结构。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/*
 * Copyright/Licensing information.
 */

/* inclusion guard */
#ifndef __VIEWER_FILE_H__
#define __VIEWER_FILE_H__

#include <glib-object.h>
/*
 * Potentially, include other headers on which this header depends.
 */

G_BEGIN_DECLS

/*
 * Type declaration.
 */
#define VIEWER_TYPE_FILE viewer_file_get_type ()
G_DECLARE_FINAL_TYPE (ViewerFile, viewer_file, VIEWER, FILE, GObject)

/*
 * Method definitions.
 */
ViewerFile *viewer_file_new (void);

G_END_DECLS

#endif /* __VIEWER_FILE_H__ */

Derivable types can be subclassed further, and their class and instance structures form part of the public API which must not be changed if API stability is cared about. They are declared using G_DECLARE_DERIVABLE_TYPE:

可继承的类型可以在之后被派生,它们的类结构和实例结构一起构成了公共API的一部分,如果要求API稳定的话这两个结构不能被修改。它们使用G_DECLARE_DERIVABLE_TYPE来声明。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/*
 * Copyright/Licensing information.
 */

/* inclusion guard */
#ifndef __VIEWER_FILE_H__
#define __VIEWER_FILE_H__

#include <glib-object.h>
/*
 * Potentially, include other headers on which this header depends.
 */

G_BEGIN_DECLS

/*
 * Type declaration.
 */
#define VIEWER_TYPE_FILE viewer_file_get_type ()
G_DECLARE_DERIVABLE_TYPE (ViewerFile, viewer_file, VIEWER, FILE, GObject)

struct _ViewerFileClass
{
  GObjectClass parent_class;

  /* Class virtual function fields. */
  void (* open) (ViewerFile  *file,
                 GError     **error);

  /* Padding to allow adding up to 12 new virtual functions without
   * breaking ABI. */
  gpointer padding[12];
};

/*
 * Method definitions.
 */
ViewerFile *viewer_file_new (void);

G_END_DECLS

#endif /* __VIEWER_FILE_H__ */

The convention for header includes is to add the minimum number of #include directives to the top of your headers needed to compile that header. This allows client code to simply #include "viewer-file.h", without needing to know the prerequisites forviewer-file.h.

头文件包含的惯例是在你的源文件所包含的GObject头文件之外尽可能的少包含其他的头文件,这允许用户代码可以简单地 #include "viewer-file.h",而不需要知道viewer-file.h所依赖的头文件。



Boilerplate code 样板源代码

In your code, the first step is to #include the needed headers:

在你的源代码中,第一步是包含所需要的头文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/*
 * Copyright information
 */

#include "viewer-file.h"

/* Private structure definition. */
typedef struct {
  gchar *filename;
  /* stuff */
} ViewerFilePrivate;

/* 
 * forward definitions
 */


If the class is being declared as final using G_DECLARE_FINAL_TYPE, its instance structure should be defined in the C file:

如果该类型使用了G_DECLARE_FINAL_TYPE声明为终类型,它的实例结构应该在C源文件中定义:

1
2
3
4
5
6
struct _ViewerFile
{
  GObject parent_instance;

  /* Other members, including private data. */
}

Call the G_DEFINE_TYPE macro (or G_DEFINE_TYPE_WITH_PRIVATE if your class needs private data — final types do not need private data) using the name of the type, the prefix of the functions and the parent GType to reduce the amount of boilerplate needed. This macro will:

  • implement the viewer_file_get_type function
  • define a parent class pointer accessible from the whole .c file
  • add private instance data to the type (if using G_DEFINE_TYPE_WITH_PRIVATE)
使用类型名、函数前缀和父类的GType通过G_DEFINE_TYPE(如果你的类型需要私有数据的话使用G_DEFINE_TYPE_WITH_PRIVATE,终类型不需要私有数据结构)来减少定义类型时所需的样板代码,该宏将:
* 实现viewer_file_get_type函数;
* 定义可以在整个c源文件中访问的父类结构指针;
* 为类型添加实例私有数据(使用G_DEFINE_TYPE_WITH_PRIVATE)。

If the class has been declared as final using G_DECLARE_FINAL_TYPE (see the section called “Boilerplate header code”), private data should be placed in the instance structure, ViewerFile, and G_DEFINE_TYPE should be used instead ofG_DEFINE_TYPE_WITH_PRIVATE. The instance structure for a final class is not exposed publicly, and is not embedded in the instance structures of any derived classes (because the class is final); so its size can vary without causing incompatibilities for code which uses the class. Conversely, private data for derivable classes must be included in a private structure, and G_DEFINE_TYPE_WITH_PRIVATEmust be used.

如果类型已经使用G_DECLARE_FINAL_TYPE声明为终类型,私有数据结构就可以直接放在实例结构VIewerFile中,并且应该使用G_DEFINE_TYPE_WITH_PRIVATE而不是G_DEFINE_TYPE来定义类型。终类型的实例结构并没有被全局导出,也没有被任何派生类型所嵌入(因为种类型没有子类),所以终类型的大小可以在不打破使用了该类型的代码的兼容性的情况下改变。相反的,可派生类型的私有数据必须被存放在私有的数据结构中,并且通过G_DEFINE_TYPE_WITH_PRIVATE来定义类型。

1
G_DEFINE_TYPE (ViewerFile, viewer_file, G_TYPE_OBJECT)

or

1
G_DEFINE_TYPE_WITH_PRIVATE (ViewerFile, viewer_file, G_TYPE_OBJECT)

It is also possible to use the G_DEFINE_TYPE_WITH_CODE macro to control the get_type function implementation — for instance, to add a call to the G_IMPLEMENT_INTERFACE macro to implement an interface.

可以通过G_DEFINE_TYPE_WITH_CODE宏来控制get_type函数的实现,例如,通过添加一个对G_IMPLEMENT_INTERFACE宏的调用来实现一个接口。




Object construction  对象的构造

People often get confused when trying to construct their GObjects because of the sheer number of different ways to hook into the objects's construction process: it is difficult to figure which is the correct, recommended way.

人们在构建他们的GObject时经常会感到困惑,因为有如此多的钩入对象构造流程的方式,很难确定哪一个是正确的、推荐的方式。


Table 4, “g_object_new” shows what user-provided functions are invoked during object instantiation and in which order they are invoked. A user looking for the equivalent of the simple C++ constructor function should use the instance_init method. It will be invoked after all the parents’ instance_init functions have been invoked. It cannot take arbitrary construction parameters (as in C++) but if your object needs arbitrary parameters to complete initialization, you can use construction properties.

表g_object_new展示了在对象实例化时调用的用户提供的函数以及他们的调用顺序,希望使用C++中简单构造函数的替代品的用户可以使用instance_init方法,它会在所有的父类型的instance_init调用后被调用,它不能像C++构造函数一样接受任意多的参数,但如果你需要的话你可以使用构造属性。


Construction properties will be set only after all instance_init functions have run. No object reference will be returned to the client of g_object_new until all the construction properties have been set.

It is important to note that object construction cannot ever fail. If you require a fallible GObject construction, you can use the GInitableand GAsyncInitable interfaces provided by the GIO library.

You should write the following code first:

构造属性仅在所有的instance_init函数被调用之后被设置,在所有的构造属性被设置之前实例的引用不会从g_object_new中返回。需要记住对象的构造永远都不能失败,如果你需要一个可以失败的对象构造,你可以使用有GIO库提供的 GInitableand GAsyncInitable接口。使用构造属性你要先写如下的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
G_DEFINE_TYPE_WITH_PRIVATE (ViewerFile, viewer_file, G_TYPE_OBJECT)

static void
viewer_file_class_init (ViewerFileClass *klass)
{
}

static void
viewer_file_init (ViewerFile *self)
{
  ViewerFilePrivate *priv = viewer_file_get_instance_private (self);

  /* initialize all public and private members to reasonable default values.
   * They are all automatically initialized to 0 to begin with. */
}

If you need special construction properties (with G_PARAM_CONSTRUCT_ONLY set), install the properties in the class_init()function, override the set_property() and get_property() methods of the GObject class, and implement them as described by the section called “Object properties”.

如果你需要特殊的构造属性(设置了 G_PARAM_CONSTRUCT_ONLY 标志),你要在class_init()中安装这些属性,然后重写GObject类结构的set_property()和get_property()方法,并实现它们。


Property IDs must start from 1, as 0 is reserved for internal use by GObject.

属性ID必须从1开始,0在内部被GObject所保留。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
enum
{
  PROP_FILENAME = 1,
  PROP_ZOOM_LEVEL,
  N_PROPERTIES
};

static GParamSpec *obj_properties[N_PROPERTIES] = { NULL, };

static void
viewer_file_class_init (ViewerFileClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);

  object_class->set_property = viewer_file_set_property;
  object_class->get_property = viewer_file_get_property;

  obj_properties[PROP_FILENAME] =
    g_param_spec_string ("filename",
                         "Filename",
                         "Name of the file to load and display from.",
                         NULL  /* default value */,
                         G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE);

  obj_properties[PROP_ZOOM_LEVEL] =
    g_param_spec_uint ("zoom-level",
                       "Zoom level",
                       "Zoom level to view the file at.",
                       0  /* minimum value */,
                       10 /* maximum value */,
                       2  /* default value */,
                       G_PARAM_READWRITE);

  g_object_class_install_properties (object_class,
                                     N_PROPERTIES,
                                     obj_properties);
}

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.

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 constructor() class method as described in the section called “Object instantiation” or, more simply, using the constructed() class method. Note that the constructed() 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 g_object_new() have been set.

如果你需要构造属性,确保你编写了与上述类似的代码,并且,确保你的构造属性在构造时没有副作用。

一些人有时需要在传给构造器的属性被设置之后才能完成一个类型的初始化,这可以使用constructor()类方法来实现,或者更简单地,使用constructed()类方法。注意只有在被G_PARAM_CONSTRUCT_ONLY 或 G_PARAM_CONSTRUCT所标志的属性被设置之后,所有传递给g_object_new()的常规属性被设置之前,constructed()虚方法才会被调用,



Object destruction 对象析构

Again, it is often difficult to figure out which mechanism to use to hook into the object's destruction process: when the lastg_object_unref function call is made, a lot of things happen as described in Table 5, “g_object_unref”.

类似地,很难找出钩入对象析构流程的最好的方法:当最后一个g_object_unref被调用之后,将有很多事情要发生。


The destruction process of your object is in two phases: dispose and 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 revival of instances in case of signal emission during the destruction sequence. See the section called “Reference counts and cycles” for more information.

你的对象的析构流程被分为两个阶段:dispose和finalize。这种划分是用来处理由于GObject所使用的引用计数机制可能出现的循环引用现象,以及处理在析构过程中由于信号发射导致的被析构的对象的临再次使用。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
struct _ViewerFilePrivate
{
  gchar *filename;
  guint zoom_level;

  GInputStream *input_stream;
};

G_DEFINE_TYPE_WITH_PRIVATE (ViewerFile, viewer_file, G_TYPE_OBJECT)

static void
viewer_file_dispose (GObject *gobject)
{
  ViewerFilePrivate *priv = viewer_file_get_instance_private (VIEWER_FILE (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 by setting the member
   * NULL; g_clear_object() does this for us.
   */
  g_clear_object (&priv->input_stream);

  /* 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 (viewer_file_parent_class)->dispose (gobject);
}

static void
viewer_file_finalize (GObject *gobject)
{
  ViewerFilePrivate *priv = viewer_file_get_instance_private (VIEWER_FILE (gobject));

  g_free (priv->filename);

  /* 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 (viewer_file_parent_class)->finalize (gobject);
}

static void
viewer_file_class_init (ViewerFileClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);

  object_class->dispose = viewer_file_dispose;
  object_class->finalize = viewer_file_finalize;
}

static void
viewer_file_init (ViewerFile *self);
{
  ViewerFilePrivate *priv = viewer_file_get_instance_private (self);

  priv->input_stream = g_object_new (VIEWER_TYPE_INPUT_STREAM, NULL);
  priv->filename = /* would be set as a property */;
}

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, by having a disposed instance revert to an inert state.

在dispose调用之后,finalize调用之前对象的方法可能会被调用,GObject并不认为这是一个程序错误。你必须检测到这种情况,在dispose之后把实例设置为一种dispose内部状态,而不是使程序终结或者警告用户。



Object methods 对象方法

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++ concepts. Those who have not had to write C++ code recently can refer to e.g.http://www.cplusplus.com/doc/tutorial/ to refresh their memories.)

  • non-virtual public methods,

  • virtual public methods and

  • virtual private methods

就像C++一样,有很多定义对象方法并且扩展它们的方式:以下内容使用了C++的术语:
* 非虚公共方法;
* 虚公共方法;
* 虚私有方法

Non-virtual public methods 非虚公共方法

These are the simplest, providing a simple method which acts on the object. Provide a function prototype in the header and an implementation of that prototype in the source file.

这是最简单的,提供了一个作用在对象上的最简单方法。在头文件中提供函数原型并在源文件中实现这个函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* declaration in the header. */
void viewer_file_open (ViewerFile  *self,
                       GError     **error);

/* implementation in the source file */
void
viewer_file_open (ViewerFile  *self,
                  GError     **error)
{
  g_return_if_fail (VIEWER_IS_FILE (self));
  g_return_if_fail (error == NULL || *error == NULL);

  /* do stuff here. */
}

Virtual public methods 虚公共方法

This is the preferred way to create GObjects with overridable methods:

  • Define the common method and its virtual function in the class structure in the public header

  • Define the common method in the header file and implement it in the source file

  • Implement a base version of the virtual function in the source file and initialize the virtual function pointer to this implementation in the object’s class_init function; or leave it as NULL for a ‘pure virtual’ method which must be overridden by derived classes

  • Re-implement the virtual function in each derived class which needs to override it

这是为GObject创建一个可重写的方法的推荐方式:
* 在公共头文件中定义通用方法以及类结构中的虚函数;
* 在源文件中实现通用方法;
* 在源文件中实现虚函数的默认版本,并在对象的class_init函数中把虚函数指针初始化为它,或者把虚函数指针设为空来实现一个必须被派生类实现的纯虚函数;

Note that virtual functions can only be defined if the class is derivable, declared using G_DECLARE_DERIVABLE_TYPE so the class structure can be defined.

注意只有可派生类才能定义虚函数,使用G_DECLARE_DERIVABLE这样类结构才能被定义。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/* declaration in viewer-file.h. */
#define VIEWER_TYPE_FILE viewer_file_get_type ()
G_DECLARE_DERIVABLE_TYPE (ViewerFile, viewer_file, VIEWER, FILE, GObject)

struct _ViewerFileClass
{
  GObjectClass parent_class;

  /* stuff */
  void (*open) (ViewerFile  *self,
                GError     **error);

  /* Padding to allow adding up to 12 new virtual functions without
   * breaking ABI. */
  gpointer padding[12];
};

void viewer_file_open (ViewerFile  *self,
                       GError     **error);

/* implementation in viewer-file.c */
void
viewer_file_open (ViewerFile  *self,
                  GError     **error)
{
  ViewerFileClass *klass;

  g_return_if_fail (VIEWER_IS_FILE (self));
  g_return_if_fail (error == NULL || *error == NULL);

  klass = VIEWER_FILE_GET_CLASS (self);
  g_return_if_fail (klass->open != NULL);

  klass->open (self, error);
}

The code above simply redirects the open call to the relevant virtual function.

上述代码只是简单地把open调用重定向为相关地虚函数。

It is possible to provide a default implementation for this class method in the object's class_init function: initialize the klass->open field to a pointer to the actual implementation. By default, class methods that are not inherited are initialized to NULL, and thus are to be considered "pure virtual".

可以在class_init函数中为这个类方法提供一个默认的实现:将klass->open域初始化为实际实现地函数指针。默认情况下,非继承地类方法被初始化为NULL,也就是纯虚方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
static void
viewer_file_real_close (ViewerFile  *self,
                        GError     **error)
{
  /* Default implementation for the virtual method. */
}

static void
viewer_file_class_init (ViewerFileClass *klass)
{
  /* this is not necessary, except for demonstration purposes.
   *
   * pure virtual method: mandates implementation in children.
   */
  klass->open = NULL;

  /* merely virtual method. */
  klass->close = viewer_file_real_close;
}

void
viewer_file_open (ViewerFile  *self,
                  GError     **error)
{
  ViewerFileClass *klass;

  g_return_if_fail (VIEWER_IS_FILE (self));
  g_return_if_fail (error == NULL || *error == NULL);

  klass = VIEWER_FILE_GET_CLASS (self);

  /* 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.
   */
  g_return_if_fail (klass->open != NULL);
  klass->open (self, error);
}

void
viewer_file_close (ViewerFile  *self,
                   GError     **error)
{
  ViewerFileClass *klass;

  g_return_if_fail (VIEWER_IS_FILE (self));
  g_return_if_fail (error == NULL || *error == NULL);

  klass = VIEWER_FILE_GET_CLASS (self);
  if (klass->close != NULL)
    klass->close (self, error);
}

Virtual private Methods 虚私有方法

These are very similar to virtual public methods. They just don't have a public function to call directly. The header file contains only a declaration of the virtual function:

这跟虚公共方法很相似,虚私有方法仅仅没有一个公共函数来调用。这个头文件仅仅包含虚函数地声明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/* declaration in viewer-file.h. */
struct _ViewerFileClass
{
  GObjectClass parent;

  /* Public virtual method as before. */
  void     (*open)           (ViewerFile  *self,
                              GError     **error);

  /* Private helper function to work out whether the file can be loaded via
   * memory mapped I/O, or whether it has to be read as a stream. */
  gboolean (*can_memory_map) (ViewerFile *self);

  /* Padding to allow adding up to 12 new virtual functions without
   * breaking ABI. */
  gpointer padding[12];
};

void viewer_file_open (ViewerFile *self, GError **error);

These virtual functions are often used to delegate part of the job to child classes:

这些虚函数一般用来代表子类的一部分工作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/* this accessor function is static: it is not exported outside of this file. */
static gboolean 
viewer_file_can_memory_map (ViewerFile *self)
{
  return VIEWER_FILE_GET_CLASS (self)->can_memory_map (self);
}

void
viewer_file_open (ViewerFile  *self,
                  GError     **error)
{
  g_return_if_fail (VIEWER_IS_FILE (self));
  g_return_if_fail (error == NULL || *error == NULL);

  /*
   * Try to load the file using memory mapped I/O, if the implementation of the
   * class determines that is possible using its private virtual method.
   */
  if (viewer_file_can_memory_map (self))
    {
      /* Load the file using memory mapped I/O. */
    }
  else
    {
      /* Fall back to trying to load the file using streaming I/O… */
    }
}

Again, it is possible to provide a default implementation for this private virtual function:

可以为这个虚私有函数提供一个默认的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
static gboolean
viewer_file_real_can_memory_map (ViewerFile *self)
{
  /* As an example, always return false. Or, potentially return true if the
   * file is local. */
  return FALSE;
}

static void
viewer_file_class_init (ViewerFileClass *klass)
{
  /* non-pure virtual method; does not have to be implemented in children. */
  klass->can_memory_map = viewer_file_real_can_memory_map;
}

Derived classes can then override the method with code such as:

派生类可以通过下述代码重写这个方法:

1
2
3
4
5
6
7
8
static void
viewer_audio_file_class_init (ViewerAudioFileClass *klass)
{
  ViewerFileClass *file_class = VIEWER_FILE_CLASS (klass);

  /* implement pure virtual function. */
  file_class->can_memory_map = viewer_audio_file_can_memory_map;
}




Chaining up 函数链

Chaining up is often loosely defined by the following set of conditions:

  • Parent class A defines a public virtual method named foo and provides a default implementation.

  • Child class B re-implements method foo.

  • B’s implementation of foo calls (‘chains up to’) its parent class A’s implementation of foo.

函数链通常由如下情况定义:
* 父类A定义了一个叫foo地公共虚方法,并提供了一个默认实现;
* 子类B重新实现了方法foo;
* B实现的foo调用A实现的foo。

There are various uses of this idiom:

  • 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 and chain up to ensure that the previous behaviour is not really modified, just extended.

  • 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 each handler is run in turn.

这个概念有多个用法:
* 你需要在不修改一个类的代码的情况下扩展它的行为。你可以创建一个子类来继承父类的实现,重新实现一个公共虚方法来修改父类的行为,然后链起来以确保父类的行为没有真正被修改,只是被扩展;
* 你需要实现责任链模式:集成树中的每一个对象的实现要链起父类的实现(典型地,在方法的开始或结尾),以确保每一个实现都被执行。

To explicitly chain up to the implementation of the virtual method in the parent class, you first need a handle to the original parent class structure. This pointer can then be used to access the original virtual function pointer and invoke it directly. [7]

要显式地链起父类实现的虚方法,你必须首先获取原始的父类结构指针,这个指针可以用来获取原始的虚函数指针然后直接调用它。


Use the parent_class pointer created and initialized by the G_DEFINE_TYPE family of macros, for instance:

使用G_DEFINE_TYPE家族宏所创建和初始化的指针parent_class,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
static void
b_method_to_call (B *obj, gint some_param)
{
  /* do stuff before chain up */

  /* 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, some_param);

  /* do stuff after chain up */
}


[7] The original adjective used in this sentence is not innocuous. To fully understand its meaning, recall how class structures are initialized: for each object type, the class structure associated with this object is created by first copying the class structure of its parent type (a simple memcpy) and then by invoking the class_init callback on the resulting class structure. Since the class_initcallback is responsible for overwriting the class structure with the user re-implementations of the class methods, the modified copy of the parent class structure stored in the derived instance cannot be used. A copy of the class structure of an instance of the parent class is needed.

注7 在这里使用的形容词“原始的”并不是平淡的:要完全理解它的含义,我们重温以下类结构是怎样初始化的:对每一个对象类型,与它相关的类结构创建时首先复制父类结构(简单的内存复制),然后在结构的类结构上调用class_init回调。由于class_init负责用用户实现的类方法重写类结构,在派生类型实例中存储的父类结构的修改版本不能被使用,所以我们需要一个原始的父类结构。


转载于:https://my.oschina.net/wsgalaxy/blog/3008512

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值