Memory Vala文档中的Gtk示例如何不';不会导致内存泄漏吗?

Memory Vala文档中的Gtk示例如何不';不会导致内存泄漏吗?,memory,memory-leaks,gtk,vala,Memory,Memory Leaks,Gtk,Vala,让我们举一个例子,从。有一个名为SyncSample的类,它派生自Gtk.Window。这个类向自身添加了两个小部件,即Gtk.SpinButton和Gtk.Scale。根据,这将建立从SyncSample到小部件的硬引用: using Gtk; public class SyncSample : Window { private SpinButton spin_box; private Scale slider; public SyncSample () {

让我们举一个例子,从。有一个名为
SyncSample
的类,它派生自
Gtk.Window
。这个类向自身添加了两个小部件,即
Gtk.SpinButton
Gtk.Scale
。根据,这将建立从
SyncSample
到小部件的硬引用:

using Gtk;

public class SyncSample : Window {

    private SpinButton spin_box;
    private Scale slider;

    public SyncSample () {

        // ...

        spin_box = new SpinButton.with_range (0, 130, 1);
        slider = new Scale.with_range (Orientation.HORIZONTAL, 0, 130, 1);
现在是有趣的部分:

        spin_box.adjustment.value_changed.connect (() => {
            slider.adjustment.value = spin_box.adjustment.value;
        });
        slider.adjustment.value_changed.connect (() => {
            spin_box.adjustment.value = slider.adjustment.value;
        });
闭包以一种艰难的方式捕获
这个
引用,并添加到这些小部件中。因此,有效地,每个小部件都有一个对
SyncSample
对象的硬引用。这已经导致硬引用循环:

  • SyncSample
    → … → <代码>Gtk.旋转按钮
→ … → <代码>同步样本
  • SyncSample
    → … → <代码>Gtk.刻度→ … → <代码>同步样本
  • 但不仅仅是这样:即使闭包没有捕获一个
    this
    引用(它们捕获了一个硬
    this
    引用),
    spin\u框
    滑块
    仍然是硬引用,导致我们进入另一个硬引用循环:

    • Gtk.SpinButton
      → … → <代码>Gtk.刻度→ … → <代码>Gtk.旋转按钮
    请解释:为什么这不会导致内存泄漏

    让我从一个免责声明开始:这个答案基于怀疑和经验观察。请随意评论你的意见,无论是对还是错

    向下滚动查看“带回家信息”


    长话短说 我做了一个小实验。我所做的是补充

    WeakRef weak_window_ref;
    WeakRef weak_slider_ref;
    
    向全球范围和

    weak_window_ref = WeakRef( this );
    weak_slider_ref = WeakRef( slider );
    
    SyncSample
    的构造函数末尾。 此外,在示例的
    main
    例程中,我添加了

    window = null;
    stdout.printf( "window leaked? %s\n", weak_window_ref.@get() != null ? "yes" : "no" );
    stdout.printf( "slider leaked? %s\n", weak_slider_ref.@get() != null ? "yes" : "no" );
    
    就在
    Gtk.main()
    返回0
    指令之间

    运行此设置时,我们看到窗口及其小部件都没有泄漏。这很好,因为这意味着参考周期被打破了——不知何故

    现在,让我们对设置做一个小小的修改:删除
    Gtk.main()
    调用,该调用位于
    window=null
    赋值之前。你可能会想,这不应该改变输出,是吗?让我们看看

    窗户漏水?对

    滑块泄漏?对

    因此,这一次的参考周期并没有被打破,并因此而泄漏。这怎么可能?我想,尤其是它是这个问题的答案:

    当小部件被销毁时,它在其他对象上保留的所有引用都将被释放:

    • 如果小部件位于容器中,它将从其父级中移除
    • 如果小部件是一个容器,那么它的所有子部件都将被递归地销毁
    当车窗关闭时,该信号可能由Gtk/Vala发出。当它被委托给窗口的小部件时,这些小部件将自己从窗口中移除。需要注意的是,
    SyncSample
    类仍然保留了对其小部件的两个硬引用(其类成员)

    所以怀疑就来了:窗口及其小部件的交互,由它们的
    destroy
    信号触发,不知何故(请参阅:“它在其他对象上的所有引用都将被释放”)会断开小部件的信号与它们的处理程序的连接。这些处理程序持有对
    SyncSample
    对象的硬引用,因为它们被定义为闭包。因此,尽管从
    SyncSample
    到其小部件的硬引用仍然完好无损,但这两个引用周期被打破:

    • SyncSample
      → … → <代码>Gtk.旋转按钮→ … → <代码>同步样本
    • SyncSample
      → … → <代码>Gtk.刻度→ … → <代码>同步样本
    因此,窗口将耗尽引用并被删除

    在这一点上,有人可能会说:难道没有第三轮硬引用吗

    • Gtk.SpinButton
      → … → <代码>Gtk.刻度→ … → <代码>Gtk.旋转按钮
    哪一个可以防止这两个小部件被破坏?这是一个合理的问题,同样,我只假设以下情况:闭包只捕获
    SyncSample
    对象的硬引用,而不捕获对
    spin_box
    滑块的引用。这些可通过
    参考进行过渡访问:

    • Gtk.SpinButton
      → <代码>同步样本
    → <代码>Gtk.刻度→ <代码>同步样本→ <代码>Gtk.旋转按钮 因此,前两个周期一旦中断,小部件就会被销毁,这反过来会将
    SyncSample
    的引用计数器重置为零,从而导致其销毁


    开放性问题 当触发窗口和小部件的销毁信号时,
    SyncSample
    的构造函数在其小部件的信号上安装的处理程序如何断开连接


    简本 将以下内容视为“带回家的信息”

    • 可能,对于子窗口小部件的回调,可以使用闭包和硬引用
      this
      ,例如,在窗口中,如果该窗口在稍后某个时间显示并特别关闭,或者其
      销毁
      信号由其他机制发出
    • 可能,定制容器小部件应该确保
      destroy
      实现释放对其子小部件的所有引用。这可能已经由
      容器
      类保证了
    • 如果您的自定义小部件维护对其他类的硬引用,而这些类又可能具有对该小部件的硬引用,那么这可能是一个错误