Vala 不同类型的构造函数
Vala different type of constructors
为什么,这三个 vala 构造函数是什么?
- class构造
- 构建
- 名称为class的方法
更具体地说,为什么从 Gtk.Builder 文件中使用第三个构造时从不调用它?
简短回答:因为这就是 GObject 的工作原理。长答案只是稍微长一点:
C 没有 objects 或构造函数,它有结构和函数。结构非常简单;它们包含字段,仅此而已。没有方法、构造函数、析构函数等。它们看起来像这样:
typedef struct Foo_ {
int bar;
char* baz;
} Foo;
对于"instantiate"一个结构,你分配必要的内存(在堆栈或堆上,我将在剩下的问题中专注于堆)并设置字段。
这很快就会变得很痛苦,即使对于我们的简单结构也是如此,因此您通常会看到有助于分配和释放结构的函数。像这样:
Foo* foo_new (int bar, char* baz) {
Foo* foo = malloc (sizeof (Foo));
/* malloc() can fail. Some libraries would return null, some would
just assume it never does. GLib-based software generally just exits
with an error, which is what we'll do here. */
if (NULL == foo) {
fprintf (stderr, "%s:%d: Unable to allocate room for struct Foo\n",
__FILE__, __LINE__);
exit (EXIT_FAILURE);
}
foo->bar = bar;
foo->baz = (NULL != baz) ? strdup (baz) : NULL;
return foo;
}
void foo_free (Foo* foo) {
if (NULL == foo)
return;
if (NULL != foo->baz)
free (foo->baz);
free (foo);
}
在 Vala 中,*_new
函数被映射到命名构造函数。对此的 Vala 绑定可能类似于:
[Compact]
public class Foo {
public Foo ();
public int bar;
public string? baz;
}
这一切都非常简单,但是当您想要 "extend" Foo
并添加一个新字段时会发生什么? C 没有任何 language-level 对 "extending" 结构的支持。 C 程序员通过在 child 结构中嵌入基本结构来解决这个问题:
typedef struct Qux_ {
struct Foo parent;
int quux;
} Qux;
这是一个相当不错的 C 级解决方案; Qux 结构的第一部分与 Foo 结构完全相同,所以当我们想将 Qux
用作 Foo
时,我们所要做的就是强制转换:
void qux_set_bar_and_qux (Qux* qux, int bar, int quux) {
((Foo*) qux)->bar = bar;
qux->quux = quux;
}
不幸的是,它在创建新实例时崩溃得很厉害。请记住,我们的 foo_new
函数在堆上分配了一片 sizeof(Foo)
字节(使用 malloc
)——quux
字段没有空间!这意味着我们不能调用 foo_new
函数。
如果你正在调用一个用 Vala 编写的库,有一种方法可以解决这个问题:除了 foo_new
函数之外,Vala 实际上会生成一个 foo_construct
函数。所以,给定类似
[Compact]
public class Foo {
public Foo (int bar, string? baz) {
this.bar = bar;
this.baz = baz;
}
}
Vala 实际生成的内容有点像这样:
void foo_construct (Foo* foo, int bar, char* baz) {
foo->bar = bar;
foo->baz = g_strdup (baz);
}
Foo* foo_new (int bar, char* baz) {
Foo* foo = g_malloc (sizeof (Foo));
foo_construct (foo, bar, baz);
return foo;
}
现在,如果我们的 Qux
class 在 Vala subclasses Foo
中,它可以调用我们的 Foo
命名构造函数:
[Compact]
public class Qux : Foo {
public Qux (int quux) {
base (0, "Hello, world");
this.quux = quux;
}
public int quux;
}
因为生成的代码实际上并没有调用foo_new
,它调用了foo_construct
:
Qux* qux_new (int quux) {
Qux* qux = g_malloc (sizeof (Qux));
foo_construct ((Foo*) qux, 0, "Hello, world");
qux->quux = quux;
}
遗憾的是,用 Vala 编写的代码 not 很少遵循此约定(grep for the 'has_construct_function' CCode attribute in the VAPIs distributed with valac).
此时您可能会想,"It's a pain, but why not just re-create the contents of the foo_new function in qux_new"。好吧,因为您可能无法访问 Foo
结构的内容。编写 Foo
的人可能不希望你弄乱他们的私有字段,因此他们可以将 Foo
设为 public headers 中的不完整类型,并保留完整的定义他们自己。
现在,让我们开始谈论GObject properties。我将略微阐述细节,但基本上它允许您注册类型,并包含一些在运行时可用的关于它们的信息。
Class注册到 GObject 的 es 可以有属性。这些在概念上有点类似于 C 结构中的字段,但该类型提供了用于加载和存储它们的回调,而不是让您的代码直接存储到一个地址。这也意味着它可以在您设置值时发出信号,以及其他一些方便的东西。
Class GObject 中的初始化相当复杂。稍后我们将对此进行更多讨论,但让我们首先从想要实例化 GObject class 的库的角度来看它。那看起来像这样:
Qux* qux = g_object_new (QUX_TYPE,
"bar", 1729,
"baz", "Hello, world",
"quux", 1701,
NULL);
它的作用可能很明显:它创建了一个 Qux
实例,并将 "bar" 属性 设置为 1729,将 "baz" 设置为 "Hello, world" , "quux" 到 1701。现在,回到 如何实例化 class。同样,这(不仅仅是)有点简化,但是......
首先,足够的内存来容纳Qux
实例(包括Foo
parentclass,现在还有GObject
class 是所有 GObject
s) 的祖先。
接下来,调用设置 "bar"、"baz" 和 "qux" 属性的回调。
接下来,调用*_constructor
函数。在 Vala 中,这被映射到 construct
块。它看起来像这样:
static GObject * foo_constructor (GType type,
guint n_construct_properties,
GObjectConstructParam construct_properties[n_construct_properties]) {
GObjectClass * parent_class = G_OBJECT_CLASS (foo_parent_class);
GObject * obj = parent_class->constructor (type, n_construct_properties, construct_properties);
Foo * self = G_TYPE_CHECK_INSTANCE_CAST (obj, TYPE_FOO, Foo);
/* The code from your construct block goes here */
return obj;
}
请注意,您无法控制此函数的参数。可以看到,每个构造函数都调用其parentclass的构造函数。我在您的构造块中的代码所在的位置添加了注释;我们很快就会回到为什么它与命名构造函数分开的原因。
现在,让我们看一下命名构造函数的代码。请记住,绝大多数库没有 *_construct
函数,因此我们可以想象一个没有(对于我们的 Qux
class):
Qux* qux_new (int bar, int quux) {
Qux* qux = g_object_new (QUX_TYPE,
"bar", bar,
"quux", quux,
NULL);
/* Code from your named constructor goes here. */
}
最后,我们了解了为什么在使用 GtkBuilder
时不调用您的命名构造函数:它不调用 qux_new
,而是调用 g_object_new
。 Calling qux_new
is an enormous pain 不了解您的图书馆,显然 GtkBuilder
了解您的图书馆是不可行的。
最后说一下class构造块。它们基本上是完全不同的东西。幸运的是,它不需要 nea只要解释一下就可以了:当类型注册到 GObject 时调用它们, 而不是 当类型的实例被实例化时。基本上,它会在您的 class 第一次实例化时被调用,而不会再被调用。一个简单的例子:
public class Foo : GLib.Object {
class construct {
GLib.message ("Hello, world!");
}
construct {
GLib.message ("%d", this.bar);
}
public int bar { get; set; default = 1729; }
}
private static int main (string[] args) {
var foo = new Foo ();
foo = new Foo ();
return 0;
}
会输出
Hello, world!
1729
1729
为什么,这三个 vala 构造函数是什么?
- class构造
- 构建
- 名称为class的方法
更具体地说,为什么从 Gtk.Builder 文件中使用第三个构造时从不调用它?
简短回答:因为这就是 GObject 的工作原理。长答案只是稍微长一点:
C 没有 objects 或构造函数,它有结构和函数。结构非常简单;它们包含字段,仅此而已。没有方法、构造函数、析构函数等。它们看起来像这样:
typedef struct Foo_ {
int bar;
char* baz;
} Foo;
对于"instantiate"一个结构,你分配必要的内存(在堆栈或堆上,我将在剩下的问题中专注于堆)并设置字段。
这很快就会变得很痛苦,即使对于我们的简单结构也是如此,因此您通常会看到有助于分配和释放结构的函数。像这样:
Foo* foo_new (int bar, char* baz) {
Foo* foo = malloc (sizeof (Foo));
/* malloc() can fail. Some libraries would return null, some would
just assume it never does. GLib-based software generally just exits
with an error, which is what we'll do here. */
if (NULL == foo) {
fprintf (stderr, "%s:%d: Unable to allocate room for struct Foo\n",
__FILE__, __LINE__);
exit (EXIT_FAILURE);
}
foo->bar = bar;
foo->baz = (NULL != baz) ? strdup (baz) : NULL;
return foo;
}
void foo_free (Foo* foo) {
if (NULL == foo)
return;
if (NULL != foo->baz)
free (foo->baz);
free (foo);
}
在 Vala 中,*_new
函数被映射到命名构造函数。对此的 Vala 绑定可能类似于:
[Compact]
public class Foo {
public Foo ();
public int bar;
public string? baz;
}
这一切都非常简单,但是当您想要 "extend" Foo
并添加一个新字段时会发生什么? C 没有任何 language-level 对 "extending" 结构的支持。 C 程序员通过在 child 结构中嵌入基本结构来解决这个问题:
typedef struct Qux_ {
struct Foo parent;
int quux;
} Qux;
这是一个相当不错的 C 级解决方案; Qux 结构的第一部分与 Foo 结构完全相同,所以当我们想将 Qux
用作 Foo
时,我们所要做的就是强制转换:
void qux_set_bar_and_qux (Qux* qux, int bar, int quux) {
((Foo*) qux)->bar = bar;
qux->quux = quux;
}
不幸的是,它在创建新实例时崩溃得很厉害。请记住,我们的 foo_new
函数在堆上分配了一片 sizeof(Foo)
字节(使用 malloc
)——quux
字段没有空间!这意味着我们不能调用 foo_new
函数。
如果你正在调用一个用 Vala 编写的库,有一种方法可以解决这个问题:除了 foo_new
函数之外,Vala 实际上会生成一个 foo_construct
函数。所以,给定类似
[Compact]
public class Foo {
public Foo (int bar, string? baz) {
this.bar = bar;
this.baz = baz;
}
}
Vala 实际生成的内容有点像这样:
void foo_construct (Foo* foo, int bar, char* baz) {
foo->bar = bar;
foo->baz = g_strdup (baz);
}
Foo* foo_new (int bar, char* baz) {
Foo* foo = g_malloc (sizeof (Foo));
foo_construct (foo, bar, baz);
return foo;
}
现在,如果我们的 Qux
class 在 Vala subclasses Foo
中,它可以调用我们的 Foo
命名构造函数:
[Compact]
public class Qux : Foo {
public Qux (int quux) {
base (0, "Hello, world");
this.quux = quux;
}
public int quux;
}
因为生成的代码实际上并没有调用foo_new
,它调用了foo_construct
:
Qux* qux_new (int quux) {
Qux* qux = g_malloc (sizeof (Qux));
foo_construct ((Foo*) qux, 0, "Hello, world");
qux->quux = quux;
}
遗憾的是,用 Vala 编写的代码 not 很少遵循此约定(grep for the 'has_construct_function' CCode attribute in the VAPIs distributed with valac).
此时您可能会想,"It's a pain, but why not just re-create the contents of the foo_new function in qux_new"。好吧,因为您可能无法访问 Foo
结构的内容。编写 Foo
的人可能不希望你弄乱他们的私有字段,因此他们可以将 Foo
设为 public headers 中的不完整类型,并保留完整的定义他们自己。
现在,让我们开始谈论GObject properties。我将略微阐述细节,但基本上它允许您注册类型,并包含一些在运行时可用的关于它们的信息。
Class注册到 GObject 的 es 可以有属性。这些在概念上有点类似于 C 结构中的字段,但该类型提供了用于加载和存储它们的回调,而不是让您的代码直接存储到一个地址。这也意味着它可以在您设置值时发出信号,以及其他一些方便的东西。
Class GObject 中的初始化相当复杂。稍后我们将对此进行更多讨论,但让我们首先从想要实例化 GObject class 的库的角度来看它。那看起来像这样:
Qux* qux = g_object_new (QUX_TYPE,
"bar", 1729,
"baz", "Hello, world",
"quux", 1701,
NULL);
它的作用可能很明显:它创建了一个 Qux
实例,并将 "bar" 属性 设置为 1729,将 "baz" 设置为 "Hello, world" , "quux" 到 1701。现在,回到 如何实例化 class。同样,这(不仅仅是)有点简化,但是......
首先,足够的内存来容纳Qux
实例(包括Foo
parentclass,现在还有GObject
class 是所有 GObject
s) 的祖先。
接下来,调用设置 "bar"、"baz" 和 "qux" 属性的回调。
接下来,调用*_constructor
函数。在 Vala 中,这被映射到 construct
块。它看起来像这样:
static GObject * foo_constructor (GType type,
guint n_construct_properties,
GObjectConstructParam construct_properties[n_construct_properties]) {
GObjectClass * parent_class = G_OBJECT_CLASS (foo_parent_class);
GObject * obj = parent_class->constructor (type, n_construct_properties, construct_properties);
Foo * self = G_TYPE_CHECK_INSTANCE_CAST (obj, TYPE_FOO, Foo);
/* The code from your construct block goes here */
return obj;
}
请注意,您无法控制此函数的参数。可以看到,每个构造函数都调用其parentclass的构造函数。我在您的构造块中的代码所在的位置添加了注释;我们很快就会回到为什么它与命名构造函数分开的原因。
现在,让我们看一下命名构造函数的代码。请记住,绝大多数库没有 *_construct
函数,因此我们可以想象一个没有(对于我们的 Qux
class):
Qux* qux_new (int bar, int quux) {
Qux* qux = g_object_new (QUX_TYPE,
"bar", bar,
"quux", quux,
NULL);
/* Code from your named constructor goes here. */
}
最后,我们了解了为什么在使用 GtkBuilder
时不调用您的命名构造函数:它不调用 qux_new
,而是调用 g_object_new
。 Calling qux_new
is an enormous pain 不了解您的图书馆,显然 GtkBuilder
了解您的图书馆是不可行的。
最后说一下class构造块。它们基本上是完全不同的东西。幸运的是,它不需要 nea只要解释一下就可以了:当类型注册到 GObject 时调用它们, 而不是 当类型的实例被实例化时。基本上,它会在您的 class 第一次实例化时被调用,而不会再被调用。一个简单的例子:
public class Foo : GLib.Object {
class construct {
GLib.message ("Hello, world!");
}
construct {
GLib.message ("%d", this.bar);
}
public int bar { get; set; default = 1729; }
}
private static int main (string[] args) {
var foo = new Foo ();
foo = new Foo ();
return 0;
}
会输出
Hello, world!
1729
1729