函数和宏的根本区别
函数和宏之间的一个根本区别,是函数引入了一个新的变量作用域,而宏没有。在函数内定义或修改的变量对函数外同名的变量没有影响,而宏与其调用者共享相同的变量范围。但是请注意,函数不会引入新的策略作用域(cmake_policy)。
如前文所述,CMake函数和宏不支持直接返回值,但有专门的命令实现这一目的。set()命令的PARENT_SCOPE关键字可用于修改调用者作用域中的变量,而不是函数中的局部变量。虽然这与从函数返回值不同,但它确实允许将一个值(或多个值)传递回调用方。
一种常见的方法是调用者给函数传变量名,函数使用上述方法修改之(这个C++传引用有些相似)。如下:
function(func resultVar1 resultVar2)
set(${resultVar1} "First result" PARENT_SCOPE)
set(${resultVar2} "Second result" PARENT_SCOPE)
endfunction()
func(myVar otherVar)
message("myVar: ${myVar}")
message("otherVar: ${otherVar}")
另一种方法是让函数自己设置变量,而不是让调用者指定变量名。但这有可能导致变量名的冲突,因为函数的编写者并不能预判使用者会不会也定义了相同的变量名。所以推荐使用前一种方法。
宏的处理方式与函数相同,通过传参指定要设置的变量的名称。唯一的区别是PARENT_SCOPE关键字不应该在宏中使用,因为宏并不会产生一个新的作用域,他本身就已经修改了调用者作用域中的变量。实际上,使用宏而不是函数的一个重要的原因是调用者需要设置许多变量。
前文中介绍了return()命令,其可以从文件或函数中提前结束处理。如前所述,return()不返回值,它只返回到父作用域。如果在函数中调用return(),则处理过程会立即返回给调用者,即跳过函数的其余部分。另一方面,在宏中return()的行为则截然不同。由于宏不引入新的作用域,return()导致调用者提前结束,并返回到调用者的调用者作用域。
这一点很要命,稍不注意就可能出意外。
同名覆盖
调用function()或macro()来定义新命令时,如果已存在具有同名的命令,则新命令会覆盖旧命令。
这一点似乎是理所当然的,但如果还想用旧命令的功能,有一个特殊技巧可以办到。即在命令前加一个下划线,如_command。此时_command指的是旧命令的功能。无论旧名称是内置命令还是自定义函数或宏,都适用。
了解此行为的人有时会试图利用此行为来增强旧命令的功能,如下所示:
function(someFunc)
# Do something...
endfunction()
function(someFunc)
if(...)
# 一些其他处理动作
else()
# 调用旧命令的功能
_someFunc()
endif()
endfunction()
如果命令只被这样重写过一次,则这种方法可以工作,但如果再次被重写,则无法再访问原始命令。也就是说假如有先后定义的N个同名命令,command,则_command单指第N-1个,而不是递归的往前调用,这可能导致严重的麻烦。如下:
function(printme) #1
message("Hello from first")
endfunction()
function(printme) #2
message("Hello from second")
_printme()
endfunction()
function(printme) #3
message("Hello from third")
_printme()
endfunction()
printme()
实际上_printme()就是指的第二个,在第二个中调用_printme(),实际上是调用自身。假如有个循环依赖于第一个命令的结果,则可能永远出不了循环,因为第一个命令已经被彻底覆盖,找不回了。
鉴于这种技巧可能导致不必要的麻烦,所以并不推荐使用。覆盖旧命令是可以的,但应该假设新的实现完全取代了旧的实现,旧的实现不会再被使用。
总结
结合前文,函数和宏相关的内容差不多了。很有意思。