Vala不同类型的构造函数

Vic*_*lio 3 constructor vala

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

  • 类构造
  • 构造
  • 类名的方法

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

nem*_*equ 10

简短的回答:因为这就是GObject的工作方式.长的答案只是一个更长的时间:

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

typedef struct Foo_ {
  int bar;
  char* baz;
} Foo;
Run Code Online (Sandbox Code Playgroud)

要"实例化"一个结构,你需要分配必要的内存(在堆栈或堆上,我会专注于堆的其他问题)并设置字段.

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

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);
}
Run Code Online (Sandbox Code Playgroud)

在Vala中,*_new函数映射到命名构造函数.对此的Vala绑定可能类似于:

[Compact]
public class Foo {
  public Foo ();

  public int bar;
  public string? baz;
}
Run Code Online (Sandbox Code Playgroud)

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

typedef struct Qux_ {
  struct Foo parent;
  int quux;
} Qux;
Run Code Online (Sandbox Code Playgroud)

这是C级别相当不错的解决方案; Qux结构的第一部分与Foo结构完全相同,所以当我们想要使用a Qux作为一个Foo全部我们要做的就是强制转换:

void qux_set_bar_and_qux (Qux* qux, int bar, int quux) {
  ((Foo*) qux)->bar = bar;
  qux->quux = quux;
}
Run Code Online (Sandbox Code Playgroud)

不幸的是,在创建新实例时它会非常糟糕.请记住,我们的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;
  }
}
Run Code Online (Sandbox Code Playgroud)

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;
}
Run Code Online (Sandbox Code Playgroud)

现在,如果我们Qux在Vala中的类子类Foo,它可以调用我们的Foo命名构造函数:

[Compact]
public class Qux : Foo {
  public Qux (int quux) {
    base (0, "Hello, world");
    this.quux = quux;
  }

  public int quux;
}
Run Code Online (Sandbox Code Playgroud)

因为生成的代码实际上没有调用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;
}
Run Code Online (Sandbox Code Playgroud)

可悲的是,代码没有在Vala中编写的很少遵循此约定(grep用于随valac分发的VAPI中的'has_construct_function'CCode属性).

此时你可能会想,"这很痛苦,但为什么不在qux_new中重新创建foo_new函数的内容".好吧,因为您可能无法访问Foo结构的内容.写作的人Foo可能不希望你搞乱他们的私人领域,所以他们可以Foo在公共标题中制作一个不完整的类型,并保持完整的定义.

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

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

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

Qux* qux = g_object_new (QUX_TYPE,
                         "bar", 1729,
                         "baz", "Hello, world",
                         "quux", 1701,
                         NULL);
Run Code Online (Sandbox Code Playgroud)

这也可能是很明显的这是什么一样:它会创建一个Qux实例,并设置"栏中的"属性设置为1729,"巴兹"到"你好,世界"和"QUUX"到1701现在,回到如何类被实例化.再次,这是(稍微)一点点简化,但......

首先,分配足够的内存来保存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;
}
Run Code Online (Sandbox Code Playgroud)

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

现在,让我们看一下命名构造函数的代码.请记住,绝大多数库都没有*_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. */
}
Run Code Online (Sandbox Code Playgroud)

最后,我们了解为什么在使用时不调用您的命名构造函数GtkBuilder:它不调用qux_new,它调用g_object_new. qux_new不了解您的图书馆的情况下,召唤是一种巨大的痛苦,显然,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;
}
Run Code Online (Sandbox Code Playgroud)

会输出

Hello, world!
1729
1729
Run Code Online (Sandbox Code Playgroud)

  • 伙计,那太棒了。我觉得我需要一支烟。感谢您的出色回答 (3认同)