此代码编译时出现警告(无关紧要的效果影响):
inline fun test(noinline f: () -> Unit) {
thread(block = f)
}
此代码无法编译(非法使用内联参数):
inline fun test(crossinline f: () -> Unit) {
thread(block = f)
}
此代码编译时出现警告(无关紧要的效果影响):
inline fun test(noinline f: () -> Unit) {
thread { f() }
}
此代码编译时没有任何警告或错误:
inline fun test(crossinline f: () -> Unit) {
thread { f() }
}
以下是我的问题:
noinline
和crossinline
之间究竟有什么区别?答案 0 :(得分:31)
请注意,某些内联函数可能会将传递给它们的lambda作为参数调用,而不是直接来自函数体,而是来自另一个执行上下文,例如本地对象或嵌套函数。在这种情况下,lambda中也不允许非本地控制流。为了表明这一点,lambda参数需要用crossinline修饰符
标记
因此,示例2.不编译,因为crossinline
仅强制执行本地控制流,而block = f
表达式违反了该表达式。示例1编译,因为noinline
不需要这样的行为(显然,因为它是一个普通的函数参数)。
示例1和3不会产生任何性能改进,因为唯一的lambda参数标记为noinline
,导致函数的inline
修饰符无用且冗余 - 编译器想要内联,但是所有可能的内容都被标记为不被内联。
考虑两个功能, A 和 B
inline fun test(noinline f: () -> Unit) {
thread { f() }
}
fun test(f: () -> Unit) {
thread { f() }
}
函数 A 的行为类似于函数 B ,因为参数f
不会被内联( B 函数不会内联test
的正文,而在 A 函数中,正文:thread { f() }
仍然内联。)
现在,在示例4中并非如此,因为crossinline f: () -> Unit
参数可以内联,它不能违反上述非本地控制流规则(如分配新值到全局变量)。如果可以内联,则编译器会假设性能得到改进,并且不会像示例3中那样发出警告。
答案 1 :(得分:7)
Q1:为什么(2)不编译但是(4)呢?
来自他们的文件:
Inlinable lambdas只能在内联函数内部调用或作为无限参数传递...
答案:
方法thread(...)
不是inline
方法,因此您无法将f
作为参数传递。
Q2:noinline和crossinline之间究竟有什么区别?
答案:
noinline
将阻止lambdas的内联。当你有多个lambda参数并且你只希望传递给内联函数的一些lambda被内联时,这就变得很有用了。
crossinline
用于标记不允许非本地返回的lambda,特别是当这样的lambda传递给另一个执行上下文时。换句话说,你不能在这样的lambda中使用return
。使用您的示例:
inline fun test(crossinline f: () -> Unit) {
thread { f() }
}
//another method in the class
fun foo() {
test{
//Error! return is not allowed here.
return
}
}
问题3:如果(3)没有产生无性能改善,为什么(4)会这样做?
答案:
那是因为你在(3)中唯一的lambda标有noinline
,这意味着你需要花费很少的成本来创建Function
对象以容纳你的身体LAMDA。对于(4),lambda仍然内联(性能改进),只是它不允许非本地返回。
答案 2 :(得分:7)
让我向您展示每个示例命令编译器执行的操作,而不是冗长的解释。让我们首先编写一些使用您的函数的代码:
fun main(args: Array<String>) {
test {
println("start")
println("stop")
}
}
现在让我们看一下您的变体。我将从您的示例test1
.. test4
中调用这些函数,并以伪代码显示以上main
函数将被编译成什么。
noinline
,block = f
inline fun test1(noinline f: () -> Unit) {
thread(block = f)
}
fun compiledMain1() {
val myBlock = {
println("start")
println("stop")
}
thread(block = myBlock)
}
首先,请注意甚至没有inline fun test1
的证据。内联函数并没有真正被“调用”:好像test1
的代码是写在main()
内的。另一方面,noinline
lambda参数的行为与没有内联的行为相同:创建一个lambda对象并将其传递给thread
函数。
crossinline
,block = f
inline fun test2(crossinline f: () -> Unit) {
thread(block = f)
}
fun compiledMain2() {
thread(block =
println("start")
println("stop")
)
}
我希望我能想到这里发生的事情:您请求将块的代码复制粘贴到需要值的位置。这只是语法垃圾。原因:无论是否使用crossinline
,您都要求将块复制粘贴到使用的位置。此修饰符仅限制您可以在块中写入的内容(无return
等)
noinline
,{ f() }
inline fun test3(noinline f: () -> Unit) {
thread { f() }
}
fun compiledMain3() {
val myBlock = {
println("start")
println("stop")
}
thread { myBlock() }
}
我们在这里回到noinline
,因此事情再简单不过了。创建一个常规的lambda对象myBlock
,然后创建另一个委托给它的常规lambda对象:{ myBlock() }
,然后将其传递给thread()
。
crossinline
,{ f() }
inline fun test4(crossinline f: () -> Unit) {
thread { f() }
}
fun compiledMain4() {
thread {
println("start")
println("stop")
}
}
最后,该示例演示了crossinline
的用途。 test4
的代码内联到main
中,块的代码内联到使用它的地方。但是,由于它是在常规lambda对象的定义中使用的,因此它不能包含非本地控制流。
Kotlin团队希望您明智地使用内联功能。内联后,已编译代码的大小会急剧爆炸,甚至达到JVM限制(每个方法最多64K字节码指令)。主要用例是高阶函数,这些函数避免了创建实际的lambda对象的开销,而只是在立即发生单个函数调用之后立即将其丢弃。
每当您声明没有内联lambda的inline fun
时,内联本身就会失去其目的。编译器会警告您。
答案 3 :(得分:1)
第一和第二个问题
怎么来(2)不编译但(4)呢?..
noinline
和crossinline
之间的差异
2. inline fun test(crossinline f: () -> Unit) {
thread(block = f)
}
4. inline fun test(crossinline f: () -> Unit) {
thread { f() }
}
两种情况都有inline
修饰符指示内联函数test
及其参数lambda f
。来自kotlin参考:
内联修饰符会影响函数本身和lambdas 传递给它:所有这些都将被内联到呼叫站点。
因此指示编译器放置代码(内联),而不是构造和调用f
的函数对象。 crossinline
修饰符仅用于内联事物:它只是说传递的lambda(在f
参数中)不应该具有非本地返回(“正常”内联lambda可能具有)。 crossinline
可以被认为是这样的东西(对编译器的指令):“内联但是有一个限制,它正在越过调用者上下文,所以确保lambda没有非本地返回。< / p>
在旁注中,thread
似乎是crossinline
的概念说明性示例,因为显然从某些代码(稍后在f
中传递)返回不同的线程可能不会影响返回来自test
,它继续在调用者线程上独立于它生成的内容执行(f
继续独立执行)..
在#4的情况下,有一个lambda(花括号)调用f()
。在#2的情况下,f
作为参数直接传递给thread
因此在#4中,可以内联调用f()
,编译器可以保证不存在非本地返回。详细说明,编译器会用它的定义替换f()
,然后代码被“包装”在封闭的lambda中,换句话说,{ //code for f() }
是另一种(包装器)lambda,它本身就是进一步的作为函数对象引用传递给thread
)。
在#2的情况下,编译器错误只是说它不能内联f
,因为它作为引用传递到“未知”(非内联)位置。 crossinline
在这种情况下变得不合适并且无关紧要,因为只有在f
内联时才能应用它。
总而言之,通过与kotlin引用中的示例(参见“高阶函数和Lambdas”)进行比较,情况2和4不同:下面的调用是等效的,其中花括号(lambda表达式)“替换“包装函数toBeSynchronized
//want to pass `sharedResource.operation()` to lock body
fun <T> lock(lock: Lock, body: () -> T): T {...}
//pass a function
fun toBeSynchronized() = sharedResource.operation()
val result = lock(lock, ::toBeSynchronized)
//or pass a lambda expression
val result = lock(lock, { sharedResource.operation() })
问题中的案例#2和#4不等同,因为在#2中没有“包装”调用f