跟我学C++中级篇——编译期的条件选择

一、编译时的条件控制

在编译时的条件控制是非常经典的应用。除了开发才习以为常的宏控制编译环境等各种预处理指令,也包括C++新标准中提供的新的预处理器扩展。当然,这必然也包括各大处理器厂商等私自扩展的相关的预处理器指令等等。尤其是在C++20中,随着模块的出现,也提供了对模块进行条件编译的处理机制。

除了上述的条件控制外,对代码中逻辑的处理、分支的判断选择等等,C++中也提供了不少的方式来解决相关的问题。除了经常提到的SFINAE技术外,C++17、C++20中也都提供了控制的方式。

也就是说,在编译时的条件控制可以分为两大类:预编译器指令和逻辑条件控制。

二、预编译器指令

条件编译是预处理器指令中最多的应用场景。它分为以下几种情况:

  1. 基础条件指令

    常见的指令包括:"#if、#ifdef、#ifndef、#else、#elif、#endif"等等,看一个简单例子:

    c 复制代码
    // 防止头文件重复包含
    #ifndef __HEADER_H__
    #define __HEADER_H__
    ...
    #endif
    
    // 平台条件控制
    #ifdef _WIN32
    #include <windows.h>
    #else
    #include <unistd.h>
    #endif
  2. 编译厂商指令

    这种一般属于各编译器自行增加相关扩展指令,当这种编译器占主流时,自然也就成为了实际上的一种标准。如CC/Clang的#pragma once等。看例子:

    c++ 复制代码
    //和前面的防止头文件重复包含功能一致
    #pragma once 
  3. 逻辑判断指令

    它一般支持常量表达式用来进行条件控制,主要包括"#if defined()"等。比如常见的库中的代码:

    c 复制代码
    // 同时判断多个宏的组合条件
     #if defined(__cplusplus) && __cplusplus >= 201102L
     #define _STD_CPP11_ 1
     #endif

    #ifdef一般用于单个的条件判断,在这种情况下#if defined与其功能一致。但#if defined支持复杂的多条件组合使用

  4. C++新标准提供的指令

    随着C++标准的不断迭代发展,也不断推出相关的条件编译指令。如C++17中的"__has_include"以及在C++20中的模块条件编译控制。C++23中的"#elifdef和#elifndef "。看下面的例子:

    c 复制代码
     #if __has_include(<tuple>)
     #include <tuple>
     #define HAS_STD_TUPLE 1
     #else
     // 引入boost中tuple
     #include <boost/tuple/tuple.hpp>
     #endif

预编译指令器的特点是语法简单、清晰,通用性好。而且支持多种平台语言。主要用于跨平台开发和头文件控制以及调试与日志管理。缺点是作用域的粒度太大,一般是文件级别,而且其和C++语法没有多大关系,多为宏指令,可读性和可维护性较差。

这种预编译的条件控制,细节有很多需要关注的情况。开发者需要在实际使用时,仔细应对,小心掉坑里去。

三、逻辑条件控制

在实际的开发中,有一种把一些计算或条件控制转移到编译期的发展方向。而这其中,特别是模板编程和元编程中,更是如此。毕竟它们可以整合在一起,实现更强大的功能。一般来说,在代码逻辑中实现条件控制有以下几种常见的机制:

  1. SFINAE
    在早期的条件分支控制中,除了一些简单的应用外,大多数需要SFINAE和std::enable_if,包括常见的模板特化、偏特化。特别是在处理一些条件的真或假进行编译选择时,std::enable_if就非常有用:

    c 复制代码
    //用来判断参数T的类型
     template <typename T, typename... Args, typename std::enable_if<std::is_integral<T>::value, int>::type * = nullptr>
     void invokeFunc(T t, Args... args) {
         ...
     }

    它一般用于约束和重载控制,用来确定生成或不生成某个函数或类。它和模板一起的是灵活的实现了各种需求。缺点就是太复杂了,可能很C++程序员整个生命周期都没有写过这类代码。

  2. if constexpr
    这个是C++17提供的一个条件编译控制的方式,它要求控制的表达式必须为常量表达式。其一般形式为:

    c 复制代码
    //c++17以后
    template <typename T>
    auto to_string(T t) {
        if constexpr(std::is_integral<T>::value) {
            return std::to_string(t);
        } else {
            return t;
        }
    }

    新的应用意味着语法的更明确、清晰,特别是对类型安全的控制。这是C++标准发展的一个重要方向。不过,缺点是它无法进行文件级别代码的处理(意味着无法代替预处理器指令)。同时,其粒度更低,导致无法控制模板的生成与否,只在生成的模板中控制分支

  3. Concepts
    概念是从C++20标准中提出的,它其实可以理解为对前面的SFINAE等机制的一种再次抽象和简化。当然,由于编译器跟进的问题,目前来看C++20的普及度还是不太够。看下面的代码:

    c 复制代码
    //函数限制
     template<typename T>
     concept func_ask = requires(T t)
     {
         t.must();
     };

    概念的优点当然是更安全、应用更明确清晰,缺点就是版本要求有点高,出现问题的定位仍然无法达到普通代码的水平,不易于处理

四、总结

C++应用的时间相对来说已经很长了,所以在历史的代码吧可以看到各种形式的条件处理机制。在实践中,当然推荐使用更先进的C++标准提供的方式,但不少的场景也离不开各种基础的编译器厂商的扩展指令。这就需要开发者根据实际情况,不断的推进在实践中的应用,根据不同的环境(特别是C++版本的不同)来综合使用各种编译条件控制。

相关推荐
SomeB1oody1 小时前
【RustyML入门】6.3. 并行归约
开发语言·后端·机器学习·rust·教程
mqiqe2 小时前
AgentScope Java 2.0 协议集成全景解析:A2A、AG-UI、Agent Protocol 三大开放协议实战指南
java·开发语言·ui
lzhdim2 小时前
12、JavaScript常见的内存泄露问题 - JavaScript学习系列文章
开发语言·前端·javascript·学习·ecmascript
脉动数据行情2 小时前
Java 实现台股 TWSE/TPEx 行情采集(个股 + K 线)
java·开发语言·twse·tpex·台股
爱学习的小邓同学2 小时前
Golang --- (1)第一个Golang程序
开发语言·后端·golang
zx_741484813 小时前
【Python 入门】面向对象基础:类、对象、成员变量与构造方法
开发语言·python
liangshanbo12155 小时前
虚拟列表深度面试题整理
java·开发语言·前端
ZISHU_9875 小时前
用 TLabel 给 SynTouch BioTac 数据做语义标注:从原始信号到结构化标注
开发语言·人工智能·python·数据·机器人触觉
略略略咯咯5 小时前
C#Unity
开发语言·unity·c#