Vala不同类型的构造函数

时间:2015-10-07 23:09:33

标签: constructor vala

为什么,以及三个vala构造函数是什么?

  • class construct
  • 构建
  • 类名
  • 的方法

更具体地说,为什么在从Gtk.Builder文件中使用它时从不调用第3个结构?

1 个答案:

答案 0 :(得分:8)

简短的回答:因为那是GObject的工作方式。答案很长很长一点:

C没有对象或构造函数,它有结构和函数。结构非常简单;它们包含字段,就是它。没有方法,构造函数,析构函数等。它们看起来像这样:

typedef struct Foo_ {
  int bar;
  char* baz;
} Foo;

To"实例化"一个结构,你分配必要的内存(在堆栈或堆上,我将关注堆的其他问题)并设置字段。

即使对于我们简单的结构,这很快就会变得很痛苦,所以你通常会看到函数来帮助分配和释放结构。像这样:

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;
}

这一切都非常简单,但是当你想要"延伸" Foo并添加新字段? C没有任何语言级支持"扩展"结构。 C程序员通过在子结构中嵌入基础结构来解决这个问题:

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;
}

现在,如果Vala中的Qux类子类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用于随valac分发的VAPI中的&has #construct_function' CCode属性)。

此时你可能会想,"这很痛苦,但为什么不在qux_new"中重新创建foo_new函数的内容。好吧,因为您可能无法访问Foo结构的内容。写Foo的人可能不希望你弄乱他们的私人字段,因此他们可以在公共标题中使Foo成为不完整的类型,并保留完整的定义。

现在,让我们开始谈论GObject properties。我将对细节有所了解,但基本上它允许您注册类型,并包含一些在运行时可用的信息。

使用GObject注册的类可以具有属性。这些在概念上有点类似于C结构中的字段,但该类型提供了用于加载和存储它们的回调,而不是直接让代码存储到地址。这也意味着当你设置一个值以及其他一些方便的东西时,它可以像想象一样发出信号。

GObject中的类初始化相当复杂。我们将在一分钟内讨论这个问题,但首先让我们从想要实例化GObject类的库的角度来看一下。这看起来像这样:

Qux* qux = g_object_new (QUX_TYPE,
                         "bar", 1729,
                         "baz", "Hello, world",
                         "quux", 1701,
                         NULL);

这可能非常明显:它会创建一个Qux实例,并设置" bar"财产到1729年," baz" to" Hello,world"和" quux"现在,回到如何实例化类。同样,这是(稍微)一点点简化,但......

首先,分配足够的内存来保存Qux实例(包括Foo父类,现在也分配了所有GObject的祖先GObject类。

接下来,回调设置" 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;
}

请注意,您无法控制此功能的参数。如您所见,每个构造函数都调用其父类的构造函数。我添加了一个注释,其中包含构造块的代码;我们很快就会回到为什么它与命名构造函数分开。

现在,让我们看一下命名构造函数的代码。请记住,绝大多数图书馆都没有*_construct功能,所以我们想象一下(对于我们的Qux课程而言):

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_newCalling qux_new is an enormous pain在不知道您的图书馆的情况下,显然GtkBuilder无法了解您的图书馆。

最后,让我们谈谈类构造块。它们基本上完全不同。幸运的是,解释它们的时间差不多:当类型向GObject注册时,它们被调用,当实例化类型的实例时,不是。基本上,它会在第一次实例化时调用,而不会再次调用。一个简单的例子:

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