困惑代表和闭包在Vala中的行为

时间:2016-08-15 09:47:42

标签: 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 = f;   // line 10
    }
}

我使用Test中定义的Func实例化Test.FUNC_0对象,并进行一些测试:

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_0 null怎么样?!
  • valac向我发出警告,指出"不支持复制代理" ,但在第10行,即this.f = f分配,所以这警告不考虑Test.FUNC_0字段。
  • 如果我删除了第一个assert并将Test.FUNC_0的{​​{1}}参数替换为new Test,则第二个() => { return 0; }会通过。那么第10行assert的错误是什么? 第10行中的闭包是复制还是不是
  • 如果是,我如何调整代码以仅在this.f = f中作为类成员引用?

我很高兴看到这个解释。 Test版本为0.28.1。

1 个答案:

答案 0 :(得分:3)

您的问题实际上与委托无关,也与单独拥有的实例无关。 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();