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

一、编译时的条件控制

在编译时的条件控制是非常经典的应用。除了开发才习以为常的宏控制编译环境等各种预处理指令,也包括C++新标准中提供的新的预处理器扩展。当然,这必然也包括各大处理器厂商等私自扩展的相关的预处理器指令等等。尤其是在C++20中,随着模块的出现,也提供了对模块进行条件编译的处理机制。
除了上述的条件控制外,对代码中逻辑的处理、分支的判断选择等等,C++中也提供了不少的方式来解决相关的问题。除了经常提到的SFINAE技术外,C++17、C++20中也都提供了控制的方式。
也就是说,在编译时的条件控制可以分为两大类:预编译器指令和逻辑条件控制。

二、预编译器指令

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

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

    // 防止头文件重复包含
    #ifndef __HEADER_H__
    #define __HEADER_H__
    ...
    #endif
    
    // 平台条件控制
    #ifdef _WIN32
    #include <windows.h>
    #else
    #include <unistd.h>
    #endif
    
  2. 编译厂商指令
    这种一般属于各编译器自行增加相关扩展指令,当这种编译器占主流时,自然也就成为了实际上的一种标准。如CC/Clang的#pragma once等。看例子:

    //和前面的防止头文件重复包含功能一致
    #pragma once 
    
  3. 逻辑判断指令
    它一般支持常量表达式用来进行条件控制,主要包括”#if defined()“等。比如常见的库中的代码:

    // 同时判断多个宏的组合条件
     #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 ”。看下面的例子:

     #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就非常有用:
    //用来判断参数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++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的普及度还是不太够。看下面的代码:
    //函数限制
     template<typename T>
     concept func_ask = requires(T t)
     {
         t.must();
     };
    
    概念的优点当然是更安全、应用更明确清晰,缺点就是版本要求有点高,出现问题的定位仍然无法达到普通代码的水平,不易于处理

四、总结

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值