跟我学C++中级篇—编译期检查 一、代码检查在前面的文章中已经对代码的管理从开始编写到最后提交甚至部署都有了较为详细的说明分析。特别对代码在编译期的检查进行过多个角度的分析说明。为了让大家对编译期检查有一个全面的理解本文将对编译期时的检查进行一个综合的分析说明。二、分析正如人们理解疾病以预防为主一样代码开发也要从最初级的源头来进行控制。代码的安全检查虽然可以辅以人工和各种工具但在编译期的检查仍然是最主要的一环。在编译期对代码进行控制可以显示的处理下面的各类问题数据类型问题这是编译器对代码检查的基础控制C是一种强类型的语言如果数据本身都无法通过检查其它也没有意义了。它包括类型大小、对齐、转换以及模板参数的属性等检查。另外在继承中的多态类型处理检查也是重要的一种情况语法问题这也是基础的检查对一些基础的语法的应用是否正确是否缺少分号、括号是否匹配、关键字是否错误等等进行基础的检查。还包括对常量和边界的控制检查等等逻辑问题随着编译期功能的愈发强大新的C标准提供了各种编译器处理的逻辑方式如if constexpr等包括早期的SFINAE技术等编译期都需要对其进行检查条件验证问题这种就更多了比如值的正确性检查、结果验证等等。常见的如static_assert以及原来一些各平台自带的类似的判断宏等接口问题接口问题之所以单独拿出来主要原因在于接口更强调的是约束。所以语法的约束是一个强检查当然也包括前面的语法和数据类型等检查。最典型的是C20推出的Concepts检查环境与依赖问题这个就属于经典的编译期检查处理当然也包括了链接时的检查。相当常见如各种库的路径、版本以及机器位数等的显式的检查编译期的检查作用非常大它可以将问题及时暴露出来防止把错误引入到运行时造成严重的破坏。一般来说编译期检查的作用主要有保证类型的安全性这是强类型语言的基础要求对语法规则的检查和相关约束机制的控制比如基础的语法的逻辑判断、转换控制以及各种约束条件和分支管理等等预防大于治疗把错误尽量消灭在编译期而不引入运行时导致各种异常甚至崩溃降低调试和维护成本无运行时成本相关的错误检查都在编译期完成不会造成运行时的效率的降低三、编译期检查的分类在编译期进行检查一般可以分为以下几类预处理这个是贯穿早期和现代C编程的重要基础检查机制。主要针对头文件依赖、编译器特性及平台检测以及错误处理和辅助支持等。它的优点在于编译的早期检查和环境依赖处理缺点是功能很基础类型检测不太可靠且有可能穿越命名空间导致污染静态处理这里包括早期的通过宏和模板等实现的检查机制也包括C11标准后的静态断言static_assert()以及constexpr与consteval等新关键字处理。主要用于编译期验证条件和强制编译期运行。它的优点在于计算前移提升了性能但缺点是会要求常量表达式处理、降低了编译效率并可能占用更多的内存类型萃取和元编程这在模板编程和元编程中应用非常多通过Type Traits进行类型推导和逻辑判断。它的优点在于非常灵活、功能强大而且C标准和库都有相当完全的支持。缺点也非常明显学习成本太高另外也增加了编译的时间SFINAE处理SFINAE技术是模板元编程中的一个重要的技术点通过和std::enable_if配合使用可以实现模板的逻辑处理及相关错误处理。优点是灵活、可扩展缺点是代码可维护性差、错误信息不容易理解出现问题也难于调试编译分支处理这是C17后引入的编译条件分支处理它对未实例化分支不作检查。优点是代码可维护程度高、逻辑清晰。由于未实现分支不处理一定程度上减少了代码膨胀缺点是标准要求必须在C17以上而且需要配合Type Traits一起使用。无法在函数外使用并要求条件必须是常量表达式概念约束Concepts其实主是一种精简版的SFINAE加强了对模板使用的约束性检查。其优点在于语法简洁、错误明确、代码易维护。缺点是标准要求更高C20以上同时学习成本仍然存在编译期检查其实可以理解为由开发者替机器来考虑如何对代码进行控制。所以就需要开发者对机器的运行有一定的了解这样才能更好的对编译期进行的各种分支判断、逻辑处理和类型检查、转换等有较强的理解从而开发中更高效、简单可维护的代码。四、例子其实编译期检查的相关的例程在前面写了很多下面综合一下再给出一个供参考//SFINAE例子--MUDUO中的例子#includeiostreamnamespace detail{templatetypename Tstructhas_no_destroy{templatetypename Cstaticchartest(decltype(C::no_destroy));templatetypename Cstaticint32_ttest(...);conststaticbool valuesizeof(testT(0))1;};}// namespace detailtemplatetypename Tstaticvoidinit(T t){T*value_newT();if(!detail::has_no_destroyT::value){std::coutis oktstd::endl;//::atexit(destroy);}}intmain(){intnum200;initint(num);return0;}//C11后templatetypename Tautolen(Tconstt)-decltype((void)(t.size()),T::size_type)//也可以使用std::enable_if修改一下表达式即可{returnt.size();}//C20templatetypename Tconcept func_askrequires(T t){t.no_destroy();};通过对比很容易可以发现相关的技术演进导致的代码编写的安全性和可维护性的提高。其它如静态检查和萃取等在前面都有专门描述不再一一的给出具体的例程有兴趣可以搜索前面的相关文章即可。五、总结编译期检查是一种重要的代码安全机制。但正如反复说过的一句话”任何事物都具有两面性“。不能把所有的解决问题的思路都放到编译期一是其能力无法满足检查只有在运行期才会出现的问题另外一个就是过多的编译期检查对于大的项目来说编译期成本也是相当高的。所以还是要根据实际情况来确定如何正确的使用编译期检查机制。推荐的原则是高频的计算较少的使用编译期检查而低频计算量较大的使用运行时检查。