在Closures中,如何通过存储在内存中或在现代函数式语言中处理的指针或引用类型捕获可变性?

时间:2018-01-08 08:38:48

标签: c++ objective-c swift compiler-construction closures

我正在编写一个用于教育目的的Transpiler。 我的翻译器从我的语言转到C语言 我现在正在编写闭包语法分析器和代码生成组件。

我看到人们说C ++中的闭包实际上是转换为未命名的结构类型,其中捕获的值是内部变量。

Here is the reference

此代码

int c = 10;
auto closure = [=] () -> void {
    std::cout << c << std::endl;
};

基本上被转化为某种类似的东西,所以他们说。

struct UNNAMED_TYPE_0 {
    int c;

    void operator() () const {
        std::cout << c << std::endl;
    }
};
// assume the closure is initialized and variables are assigned

如果有人想在闭包执行时改变int c,他/她必须将此变量作为ref [&c] () -> void { /* mutation comes here */}传递。但问题是如果我们在函数内声明int c并在函数内创建该闭包

function<void()> aFunction() {
    int c = 10;
    auto closure = [&c] () -> void { c = 100; }
    return closure;
}

aFunction() ();
捕获了

int c但是只要aFunction堆栈被销毁,int c也会被销毁。这意味着,如果我们尝试在解除分配的地址上写入,我们可能希望运行segmentation fault(core dumped)指针错误。

Java

// suppose this callback interface exists
public interface VoidCallback {
    public void method();
} 


public void aMethod() {
    int c = 10;
    VoidCallback callback = () -> c = 10; /* this gives an error */
    // local variables referenced from a lambda expression must be final or effectively final
}

Java处理这样的闭包并确保闭包捕获没有变异(让我们说隐式捕获)。含义Java传递闭包捕获副本而不是ref。对于引用或类类型,仅将Object指针作为副本传递。虽然指针引用不会变异,但您可以改变指针指向的对象内的内容。这与前者基本相同。

Objective-C

__block int c = 0;
// they say, this `int c` is allocated in heap instead of stack
// so that it exists until the closure is finished executing.
void (^ closure) (void) = ^void() {
    c = 10; // this is valid
    // this actually changed the `int c`'s value
}; 

Swift

var a : Int = 10;
var closure = { [] () -> Void in
    a = 10; // this is valid by default
    // unless `var a` is declared as `let a`
};

因此,这意味着,Objective-C和Swift将原始捕获列表分配为指针。这样他们就可以变异了。

P.S:请注意,Swift闭包捕获列表仅适用于类或ref类型,但我的意思是隐式捕获原始类型。

这是

__block int c = 0;
// they say, this `int c` is allocated in heap instead of stack
// so that it exists until the closure is finished executing.
void (^ closure) (void) = ^void() {
    c = 10; // this is valid
    // this actually changed the `int c`'s value
}; 

与此基本相同

int * c = malloc(sizeof(int));
*c = 0;
void (^ closure) (void) = ^void() {
    *c = 10;
    if (c) {
        free(c);
        c = NULL;
    }
}; 

一旦关闭完成就释放指针变量我认为太糟糕了 如果有很多闭包指向变量并且在执行时会发生变异会怎么样? 如果那些闭包传递或在不同的线程中执行会怎么样?

我想出了一个使用参考计数技术的解决方案 当创建一个改变变量的闭包时,该变量将被保留 当一个变量变量的闭包被销毁时,该变量将被释放 如果没有关闭,变量将被真正解除分配。 为了确保线程安全,我会锁定并解锁计数器变量地址,因为闭包操作引用计数技术。

如果还有其他技巧,请指导我 任何语言的任何解释都非常感谢。

目前,我对汇编语言一无所知。

对于版主, 因为这个问题是一种研究,我请求你不要过于宽泛。

1 个答案:

答案 0 :(得分:2)

跟着我说:“我正在编写一个用于教育目的的Transpiler。我的翻译器从我的语言转换为C语言。”现在,这意味着您的语言规范定义了它应该如何运作!我们无法告诉您语言应该如何运作。

现在,您已经找到了许多选项:

  • C ++不对局部变量做任何特殊处理。如果你保留对它的引用并在它超出范围时使用它,运气不好。这就是C ++的精神,不会给你带来任何开销,但是如果你不注意的话就让你自己开枪。
  • Java只是检查代码并告诉您是否正在尝试执行它认为不保证有效的任何操作,否则会给您一个错误。即使你想要它,它也不允许你用脚射击自己。
  • 其他语言似乎将范围有限的局部变量转换为基于堆的局部变量。我不确定他们的对象模型,但是在Python中你根本没有类似于C ++局部变量或Java原始类型的东西,就像你没有确定性的析构函数调用一样(你在那里使用with得到类似的东西,只是为了完整性),所以这个没有任何区别。这些语言给你带来了开销,以保证你没有任何悬空引用(也许即使你真的不需要它)。

现在,首先要确定哪一种最适合您语言的对象模型。只有这样,问题就出现了如何最好地实现它。关于实现,有许多不同的方法。使用引用计数器是一个(使用无锁,原子操作实现),使用链表是另一个,或使用垃圾收集器。