Lambda 对代理和闭包在Vala中的行为感到困惑

Lambda 对代理和闭包在Vala中的行为感到困惑,lambda,vala,Lambda,Vala,我创建了一个最小的示例,它再现了一种奇怪的Vala行为,我不理解,我想解释一下 类Test的构造函数获取一个Func并使用它初始化其类成员f: public class Test { public delegate int Func(); public static Func FUNC_0 = () => { return 0; }; public Func f; public Test( Func f ) { this.f =

我创建了一个最小的示例,它再现了一种奇怪的Vala行为,我不理解,我想解释一下

Test
的构造函数获取一个
Func
并使用它初始化其类成员
f

public class Test
{
    public delegate int Func();
    public static Func FUNC_0 = () => { return 0; };

    public Func f;

    public Test( Func f )
    {
        this.f = f;   // line 10
    }
}
我使用
Test.Func\u 0
中定义的
Func
实例化
Test
对象,并执行一些测试:

public static void main()
{
    assert( Test.FUNC_0 != null );   // first assert
    var t = new Test( Test.FUNC_0 );
    assert( t.f != null );           // second assert
}
这有什么奇怪的

  • 首先,事实证明,
    Test.FUNC\u 0
    null
    。怎么会这样
  • valac
    给了我一个警告,“不支持复制委托”,但是在第10行,这是
    this.f=f
    赋值,因此此警告不考虑
    Test.FUNC\u 0
    字段
  • 如果删除第一个
    断言
    ,并将
    新测试
    Test.FUNC\u 0
    参数替换为
    ()=>{return 0;}
    ,则第二个
    断言
    通过。那么第10行中的这个.f=f有什么问题?第10行的闭包是否已复制
  • 如果是,我将如何调整代码,使其仅保留一个引用作为
    Test
    中的类成员

我真的很高兴看到解释。valac的版本是0.28.1。

您的问题实际上与委托无关,而与单独拥有的实例有关。Vala中的所有非原语类型都是有主类型或无主类型。某些类(包括从
GLib.Object
派生的类)可以有多个所有者。当需要类的副本时,目标类上的引用计数将递增。其他类(包括
string
)和结构只能有一个所有者,但附带了一个复制函数,允许生成该类的副本。委托和某些类(如
FileStream
)也只有一个所有者,但不能复制

无法复制委托的原因是委托包含三条信息:回调函数、一些上下文数据,以及上下文数据的析构函数。没有复制功能

由于默认情况下参数是无主的,
this.f=f
正试图将它不拥有的委托复制到它拥有的引用中。这将是内存不安全的,因为它要么在对象的生命周期之后保存引用,要么可以调用析构函数两次

您有两个选择:

public class Test
{
    public delegate int Func();
    public static Func FUNC_0 = () => { return 0; };

    public Func f;

    public Test( owned Func f )
    {
        this.f = (owned) f;   // you are just moving the delegate, not copying it.
    }
}
或者:

public class Test
{
    public delegate int Func();
    public static Func FUNC_0 = () => { return 0; };

    public unowned Func f;

    public Test( Func f )
    {
        this.f = f;   // You are copying an unowned reference to another
        // unowned reference, which is fine. Memory management is now your
        // job and not Vala's.
    }
}
第二个有点危险。例如,以下代码将编译并损坏内存:

Test? t = null;
if (true) {
  int x = 5;
  Func f = () => { return x; };
  t = new Test(f);
}
t.f();