【问题标题】:Kotlin's crossinline keywordKotlin 的 crossinline 关键字
【发布时间】:2021-01-26 07:35:40
【问题描述】:

我已经阅读了this question,但我对crossinline 关键字有一个更基本的问题。我不太确定它正在解决什么问题以及它是如何解决的。

来自the Kotlin Docs

请注意,某些内联函数可能不直接从函数体调用作为参数传递给它们的 lambda,而是从另一个执行上下文(例如本地对象或嵌套函数)调用。在这种情况中,也不允许在 lambda 中使用非本地控制流。 为了表示,lambda参数需要用crossinline修饰符标记:

[强调添加]

这句话对我来说是模棱两可的。首先,我很难想象“这种情况”是什么意思。我对问题所在有一个大致的了解,但无法举出一个很好的例子。

其次,短语“表明”可以有多种读法。表明什么?那个特殊情况是不允许的?它允许的吗?给定函数定义中的非本地控制流是否允许(或不允许)?

简而言之,我很难弄清楚使用 this 的真正上下文是什么,使用它与客户交流什么,以及应用这个关键字的预期结果是什么。

【问题讨论】:

    标签: kotlin


    【解决方案1】:

    首先,我很难想象“这种情况”是什么意思。我对问题所在有一个大致的了解,但无法举出一个很好的例子。

    这是一个例子:

    interface SomeInterface {
        fun someFunction(): Unit
    }
    
    inline fun someInterfaceBy(f: () -> Unit): SomeInterface { 
        return object : SomeInterface {
            override fun someFunction() = f() 
            //                            ^^^
            // Error: Can't inline 'f' here: it may contain non-local returns. 
            // Add 'crossinline' modifier to parameter declaration 'f'.
        }
    }
    

    这里,传递给someInterfaceBy { ... } 的函数被内联在实现SomeInterface 的匿名类中。 someInterfaceBy 的每个调用站点的编译都会生成一个新类,该类具有 someFunction() 的不同实现。

    要查看可能出现的问题,请考虑拨打someInterfaceBy { ... }

    fun foo() {
        val i = someInterfaceBy { return }
        // do something with `i`
    }
    

    在内联 lambda 中,return is non-local 实际上意味着 foo 返回。但是由于没有调用 lambda 并且泄漏到对象 i 中,foo 返回可能绝对没有意义:如果在 foo 之后调用 i.someFunction()(因此是 lambda)会怎么样已经返回或什至在不同的线程中?

    一般来说,'这样的情况' 是指inline 函数,它们不是在它们自己的主体中调用它们的函数参数(实际上,即考虑到其他内联函数),而是在它们声明的其他一些函数中,就像在非内联 lambda 和匿名对象中一样。


    其次,短语“表明这一点”可以有多种读法。表明什么?那一个特定的情况是不允许的?那是允许的吗?给定函数定义中的非本地控制流是否允许(或不允许)?

    这正是我上面描述的问题在 Kotlin 语言设计中得到解决的方式:每当 inline 函数打算将其函数参数内联到某个不能被就地调用但存储后调用,inline函数的参数应标记为crossinline,表示此处传递的lambdas不允许非本地控制流。

    【讨论】:

    • 因此,crossinline 关键字可防止 lambda 包含 return 关键字(导致非本地返回),这对于函数中使用 lambda 的方式没有确定性意义。
    • 我还假设标签处的返回不受使用crossinline 的影响,对吗?
    • @melston,是的,只要它是本地。也就是说,someList.forEach { item -> someCrossInline { return@forEach item } } 是不允许的,因为这是 crossinline lambda 的非本地返回,但 someList.forEach { item -> someCrossInline { return@someCrossInline item } } 很好(抱歉评论中有大代码)。
    【解决方案2】:

    问题:非本地return

    我们先通过一个简单的例子来理解非本地return的问题:

    fun doSomething() {
        println("Before lambda")
        doSomethingElse {
            println("Inside lambda")
            return // This is non-local return
        }
        println("After lambda")
    }
    
    inline fun doSomethingElse(lambda: () -> Unit) {
        println("Do something else")
        lambda()
    }
    

    非本地return

    在上面的代码中,return 语句被称为非本地返回,因为它对于调用它的函数来说不是本地的。这意味着这个return 语句是doSomething() 函数的本地语句,而不是调用它的lambda 函数。因此,它终止了当前函数以及最外层的函数。

    本地return

    如果你只是想从 lambda 返回,你会说return@doSomethingElse。这称为local return,它是指定它的函数的本地。

    问题

    现在这里的问题是编译器跳过了非本地return 语句之后的行。 doSomething() 的反编译字节码如下所示:

    public static final void doSomething() {
        System.out.println("Before lambda");
        System.out.println("Doing something else");
        System.out.println("Inside lambda");
    }
    

    请注意,没有为行println("After lambda") 生成语句。这是因为我们在 lambda 中有非本地的return,编译器认为return 语句之后的代码没有意义。


    解决方案:crossinline关键字

    crossinline

    这种情况(如上面提到的问题)中,解决方案是禁止lambda 内的非本地return。为此,我们将lambda 标记为crossinline

    inline fun doSomethingElse(crossinline lambda: () -> Unit) {
        println("Doing something else")
        lambda()
    }
    

    不允许使用非本地return

    当您使用 crossinline 关键字时,您是在告诉编译器,“如果我不小心在嵌套函数或本地对象中使用了非本地 return,请给我一个错误。”:

    fun doSomething() {
        println("Before lambda")
        doSomethingElse {
            println("Inside lambda")
            return                  // Error: non-local return
            return@doSomethingElse  // OK: local return
        }
        println("After lambda")
    }
    

    现在编译器按预期生成字节码:

    public static final void doSomething() {
        System.out.println("Before lambda");
        System.out.println("Doing something else");
        System.out.println("Inside lambda");
        System.out.println("After lambda");
    }
    

    就是这样!希望我能让它更容易理解。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-10-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-06-03
      相关资源
      最近更新 更多