混淆委托和闭包在 Vala 中的行为
Confused how Delegates and Closures behave in Vala
我创建了一个最小示例,它重现了一个奇怪的 Vala 行为,我不明白,希望得到解释。
class Test
的构造函数接受一个 Func
并使用它来初始化它的 class 成员 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_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_0
是null
。 那怎么可能?!
valac
给我一个警告 "copying delegates is not supported",但是在第 10 行,这是 this.f = f
赋值,所以这个警告没有'考虑 Test.FUNC_0
字段。
- 如果我删除第一个
assert
并将 new Test
的 Test.FUNC_0
参数替换为 () => { return 0; }
,则第二个 assert
通过。那么第 10 行的 this.f = f
有什么问题呢? 是第10行的闭包复制还是不是?
- 如果是,我将如何调整代码以仅保留作为
Test
中的 class 成员的引用?
我真的很高兴看到这个解释。 valac
版本是 0.28.1.
您的问题实际上与委托无关,而与单独拥有的实例有关。 Vala 中的所有非原始类型要么是拥有的,要么是无主的。某些 classes(包括派生自 GLib.Object
的)可以有多个所有者。当需要 class 的副本时,目标 class 上的引用计数会增加。其他 classes(包括 string
)和结构只能有一个所有者,但带有一个复制函数,允许生成 class 的副本。代表和某些 classes(如 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();
我创建了一个最小示例,它重现了一个奇怪的 Vala 行为,我不明白,希望得到解释。
class Test
的构造函数接受一个 Func
并使用它来初始化它的 class 成员 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_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_0
是null
。 那怎么可能?! valac
给我一个警告 "copying delegates is not supported",但是在第 10 行,这是this.f = f
赋值,所以这个警告没有'考虑Test.FUNC_0
字段。- 如果我删除第一个
assert
并将new Test
的Test.FUNC_0
参数替换为() => { return 0; }
,则第二个assert
通过。那么第 10 行的this.f = f
有什么问题呢? 是第10行的闭包复制还是不是? - 如果是,我将如何调整代码以仅保留作为
Test
中的 class 成员的引用?
我真的很高兴看到这个解释。 valac
版本是 0.28.1.
您的问题实际上与委托无关,而与单独拥有的实例有关。 Vala 中的所有非原始类型要么是拥有的,要么是无主的。某些 classes(包括派生自 GLib.Object
的)可以有多个所有者。当需要 class 的副本时,目标 class 上的引用计数会增加。其他 classes(包括 string
)和结构只能有一个所有者,但带有一个复制函数,允许生成 class 的副本。代表和某些 classes(如 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();