【问题标题】:Vala different type of constructorsVala 不同类型的构造函数
【发布时间】:2016-01-05 09:01:52
【问题描述】:

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

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

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

【问题讨论】:

    标签: constructor vala


    【解决方案1】:

    简短回答:因为这就是 GObject 的工作方式。长答案只是稍微长一点:

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

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

    要“实例化”一个结构,您需要分配必要的内存(在堆栈或堆上,我将在剩下的问题中关注堆)并设置字段。

    这很快就会变得很痛苦,即使对于我们的简单结构也是如此,因此您通常会看到帮助分配和释放结构的函数。像这样的:

    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 编写的代码很少遵循这种约定(用 valac 分发的 VAPI 中的 'has_construct_function' CCode 属性的 grep 属性)。

    此时您可能会想,“这很痛苦,但为什么不直接在 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”设置为“Hello, world”,将“quux”设置为1701。现在,回到如何类被实例化。同样,这(不止)有点简化,但是……

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

    接下来,调用设置“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
    

    【讨论】:

    • 老兄,太棒了。我觉得我需要一支烟。感谢您的出色回答
    猜你喜欢
    • 1970-01-01
    • 2012-05-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-01-30
    • 2017-06-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多