为何C++ Lambda与iOS Block无法完全兼容互用?
我是iOS编程新手,正在学习Block与C++ Lambda。查阅资料得知二者功能相近,Lambda可替代Block,但除了捕获子句及C++特有的mutable、throw可选关键字外,我发现两处实际差异:
- 可将Block或Lambda赋值给
std::function类型变量,但无法将Lambda赋值给Block类型变量,编译会报错; - 无法直接
typedefLambda,只能借助std::function实现等效定义。
相关代码
#include <iostream> #include <functional> #include <pthread.h> #include <xpc/xpc.h> #include <dispatch/dispatch.h> #define SHOW_COMPILE_ERROR // add comments on next line to see the compile error #undef SHOW_COMPILE_ERROR // You cannot create a lambda typedef, but you can create a general purpose // polymorphic function wrapper from std::wrapper // e.g. typedef [] (xpc_object_t) -> void do_custom_message_l_t; is not legal // but the std::function declaration is legal - why? typedef std::function<void(xpc_object_t)> do_custom_msg_l_t; // block typedefs are legal as below typedef void (^do_custom_msg_b_t)(xpc_object_t); // create a function declaration with a callback using either // approaches - the block typedef or the std::function wrapper // just as well xpc_object_t build_message(int func, do_custom_msg_b_t&); xpc_object_t build_message(int func, do_custom_msg_l_t&); // create a couple of global variables of the block or std::function type do_custom_msg_b_t do_custom_msg_b; do_custom_msg_l_t do_custom_msg_l, do_custom_msg_l2; #if defined(SHOW_COMPILE_ERROR) do_custom_msg_b_t do_custom_msg_b2; #endif int main(int argc, char *argv[]) { // assign a block to the block type global do_custom_msg_b = ^(xpc_object_t custom_message) { auto p= xpc_copy_description(custom_message); std::cerr << "Custom Message Objects: " << p << std::endl; free(p); }; // assign a std::function type a lambda definition - cool. // but change the variable to do_custom_msg_b and suddenly there // is an error! So you can assign a lambda definition to a block // variable type. Very weird. do_custom_msg_l = [] (xpc_object_t custom_message) { auto p= xpc_copy_description(custom_message); std::cerr << "Custom Message Objects: " << p << std::endl; free(p); }; #if defined(SHOW_COMPILE_ERROR) // this is not legal - why?, if it is legal to assign a block to a lambda // /std::function then why not the other way around? do_custom_msg_b2 = [] (xpc_object_t custom_message) { auto p= xpc_copy_description(custom_message); std::cerr << "Custom Message Objects: " << p << std::endl; free(p); }; #endif // we are now assigning a block to std::function wrapper (lambda) // this works just fine . . . weird! do_custom_msg_l2 = ^(xpc_object_t custom_message) { auto p= xpc_copy_description(custom_message); std::cerr << "Custom Message Objects From Blocks Assigned to std::function: " << p << std::endl; free(p); }; // in conclusion blocks can be assigned lambda/std::functions but std::functions/lambdas // cannot be assigned to block types (typedefs). So there is a false equivilence between // lambdas and blocks // here is how lambdas are normally defined and executing them // is very easy auto mylambda = [] (void *ptr){ std::cout << "This lambda " << ptr << " is running in thread " << pthread_self() << std::endl; sleep(1); }; // here is the block equivalent to the above lambda void (^myblock) (void) = ^{ std::cout << "This block is running in thread " << pthread_self() << std::endl; sleep(1); }; // call that lambda with its address as a parameter mylambda(&mylambda); // call the block myblock(); // dispatch the lambda - cool! dispatch_async_f(dispatch_get_main_queue(), &mylambda, mylambda); // dispatch the block - very cool! dispatch_async(dispatch_get_main_queue(), myblock); // build an object using the block callback xpc_object_t msg = build_message(1, do_custom_msg_b); // build a message utilizing the lambda call msg = build_message(2, do_custom_msg_l); // call the lambda variable that was assigned a block // this works - very cool! msg = build_message(3, do_custom_msg_l2); #if defined(SHOW_COMPILE_ERROR) // nope, this won't even build msg = build_message(4, do_custom_msg_b2); #endif dispatch_main(); return 0; } /** * @brief build a new message header to be sent to the server * @param func The function id of what command to be sent * @param do_custom_msg block that can optionally customize this created message * @returns the xpc object built * @note objects created with this function must be released */ xpc_object_t build_message(int func, do_custom_msg_b_t& do_custom_msg) { xpc_object_t msg = xpc_dictionary_create_empty(); xpc_dictionary_set_uint64(msg, "typ", func); xpc_dictionary_set_date(msg, "date", time(nullptr)); xpc_dictionary_set_uint64(msg, "serno", 1); if(nullptr != do_custom_msg) { do_custom_msg(msg); } return msg; } /** * @brief build a new message header to be sent to the server * @param func The function id of what command to be sent * @param do_custom_msg block that can optionally customize this created message * @returns the xpc object built * @note objects created with this function must be released */ xpc_object_t build_message(int func, do_custom_msg_l_t& do_custom_message ) { xpc_object_t msg = xpc_dictionary_create_empty(); xpc_dictionary_set_uint64(msg, "typ", func); xpc_dictionary_set_date(msg, "date", time(nullptr)); xpc_dictionary_set_uint64(msg, "serno", 1); if(nullptr != do_custom_message) { do_custom_message(msg); } return msg; }
开发环境
- CLion 2024.1.4
- Apple clang version 15.0.0
- 2023 Mac Studio(运行Sonoma 14.6.1)
编译错误信息
/Applications/CLion.app/Contents/bin/cmake/mac/aarch64/bin/cmake --build "/Users/marksanderson/Projects/codeing tests/cmake-build-debug" --target codeing_tests -j 22 [1/2] Building CXX object CMakeFiles/codeing_tests.dir/main.cpp.o FAILED: CMakeFiles/codeing_tests.dir/main.cpp.o /Library/Developer/CommandLineTools/usr/bin/c++ -g -std=gnu++20 -arch arm64 -isysroot /Library/Developer/CommandLineTools/SDKs/MacOSX14.4.sdk -fcolor-diagnostics -MD -MT CMakeFiles/codeing_tests.dir/main.cpp.o -MF CMakeFiles/codeing_tests.dir/main.cpp.o.d -o CMakeFiles/codeing_tests.dir/main.cpp.o -c '/Users/marksanderson/Projects/codeing tests/main.cpp' /Users/marksanderson/Projects/codeing tests/main.cpp:59:24: error: assigning to 'do_custom_msg_b_t' (aka 'void (^)(xpc_object_t)') from incompatible type '(lambda at /Users/marksanderson/Projects/codeing tests/main.cpp:59:24)' do_custom_msg_b2 = [] (xpc_object_t custom_message) { ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1 error generated. ninja: build stopped: subcommand failed.
根本原因解析
1. 无法将Lambda赋值给Block类型变量的原因
Block是Apple专为C/C++/Objective-C推出的语言扩展特性,它拥有独立的类型系统和运行时机制——比如Block需要支持自动拷贝到堆、引用计数管理等,底层有特定的内存布局要求。
而C++ Lambda是标准C++的特性,本质是编译器为每个Lambda表达式生成的匿名仿函数类的实例,每个Lambda的类型都是独一无二的匿名类型,和Block类型不存在任何隐式转换关系。
clang编译器仅实现了Block到std::function的隐式转换(因为std::function是通用可调用对象包装器,只要签名匹配就能适配),但反过来,Lambda的底层实现完全不满足Block的运行时和内存布局要求,编译器也没有提供Lambda转Block的规则,因此直接赋值会触发类型不兼容错误。
2. 无法直接typedef Lambda的原因
Lambda的类型是编译器自动生成的匿名类类型,每个Lambda表达式对应一个完全唯一、没有名称的类型。而typedef需要绑定一个有明确名称的类型,自然无法直接对匿名的Lambda类型进行typedef。
std::function是标准库提供的多态函数包装器,它能封装所有符合指定签名的可调用对象(包括Lambda、Block、普通函数等),它本身是一个有明确名称的模板类,因此可以用typedef定义特定签名的std::function类型,实现类似"Lambda类型别名"的效果,但这本质是包装器的类型,并非Lambda本身的类型。
内容的提问来源于stack exchange,提问作者Mark F. Sanderson

