C++中constexpr 与 explicit关键字使用实战样例

 更新时间:2025年12月13日 14:09:36   作者:点云SLAM  
文章详细介绍了constexpr和explicit关键字在C++中的用途和用法,constexpr用于声明实体可以在编译期求值,而explicit用于阻止不期望的隐式类型转换,本文结合实例代码介绍的非常详细,感兴趣的朋友跟随小编一起看看吧

概述

  • constexpr:声明“该实体可以在编译期求值”,随着 C++ 标准被逐步放宽,现在能在更多场景写编译期函数/构造器/变量。C++20 还新增了 consteval(强制编译期)与 constinit(强制常量初始化)。
  • explicit:用于阻止不期望的隐式转换。除了对构造函数适用外,C++11 开始可标注转换运算符(conversion operators);C++20 引入了条件 explicit(expr) 能按表达式决定是否为显式。

一、constexpr深度讲解

1. 含义(核心语义)

constexpr 表示:函数/构造器/变量在满足条件时可以在编译期求值,从而能用作常量表达式(用于数组维度、模板非类型参数、static_assert 等)。

  • 对象(变量)使用 constexpr:其初始化器必须是常量表达式(直到 C++14 的限制放宽后更灵活)。
  • 函数/构造函数使用 constexpr:表明在满足参数均为常量表达式的情况下,函数可被用于编译期求值;否则仍可在运行时调用。

2. 演进变化

constexpr 的能力随标准放宽:

  • C++11:首次引入,函数体必须是单一 return(很受限)。
  • C++14:允许更复杂的函数体(局部变量、循环、分支),constexpr 函数功能大增。
  • C++17:语义继续放宽,数组/结构体初始化等更多场合支持常量求值。
  • C++20:引入 consteval(强制在编译期求值)和 constinit(强制静态变量为常量初始化),并继续扩展 constexpr 支持(例如允许虚函数在某些情形下为 constexpr)。

新关键词对比(C++20)

  • constexpr:可以在编译期或运行期求值(视调用上下文而定)。
  • consteval必须在编译期求值,若在运行期调用则编译错误。用来写“即时常量函数”。
  • constinit:用于静态/线程局部变量,强制其进行常量初始化(防止静态初始化次序问题)。

3.constexpr的常见用法示例

3.1constexpr变量

constexpr int square(int x) { return x * x; }   // C++14 起允许复杂体
constexpr int five = 5;
constexpr int twentyfive = square(five);         // 编译期求值
static_assert(twentyfive == 25);

3.2constexpr函数(运行期与编译期两用)

constexpr int fib(int n) {
    if (n <= 1) return n;
    return fib(n-1) + fib(n-2);
}
int main() {
    constexpr int f5 = fib(5);       // 编译期
    int k; cin >> k;
    int r = fib(k);                  // 运行期也可调用
}

3.3constexpr构造函数(常用于字面类型)

struct Point {
    double x, y;
    constexpr Point(double a, double b) : x(a), y(b) {}
    constexpr double norm2() const { return x*x + y*y; }
};
constexpr Point p{3.0, 4.0};
static_assert(p.norm2() == 25.0);
注意
  • 直到 C++20,constexpr 成员函数和构造器的语义被逐步增强(允许更多操作,如改变 constexpr 成员变量等)。

4.consteval与constinit

consteval int must_be_constexpr(int n) { return n*2; }  // 必须在编译期调用
constinit int g = must_be_constexpr(10);                // g 在编译期初始化

consteval 用于那些希望强制在编译期完成的计算(例如生成编译期表或做元编程检查)。

5. 常见误区与陷阱(constexpr)

  • 不是所有 constexpr 函数调用都会在编译期执行:是否在编译期执行取决于调用时传入的参数是否为常量表达式以及使用场景(需要编译期值时才强制求值)。
  • constexpr 函数内部不能使用非 constexpr 的操作(例如动态分配、IO),否则在尝试编译期求值时会失败(但该函数仍可在运行期被调用,除非用 consteval)。
  • 不要把 constexpr 当作“强制内联或优化提示” —— 它的本意是可编译期求值;优化与否由编译器决定。
  • constexprnoexcept 互不替代,但常见 constexpr 函数也声明 noexcept(如果它们无异常)以改善使用场景。

6. 实战建议(constexpr)

  • 对纯计算、常量生成函数使用 constexpr:可在编译期做更多检查并得到更高性能。
  • 若函数必须在编译期求值(设计要求),使用 consteval
  • 对类提供 constexpr 构造函数 / 方法以便可在编译期创建字面量对象(例如元数据、表格)。
  • 在大型项目中谨慎使用 constexpr 以避免过度编译期计算导致编译时间增加。

有关 constexpr 的标准细节参考:cppreferenceconstexpr 条目。

二、explicit深度讲解

1. 含义(核心语义)

explicit 的目的是 禁止编译器执行某些隐式转换,从而避免意外、难以察觉的类型转换错误。它可以修饰:

  • 构造函数(阻止从单参数构造函数的隐式转换) — 自 C++98。
  • 用户定义的转换运算符(conversion operator)— 自 C++11。
  • C++20 引入:explicit(expression),可以根据编译期表达式条件性地使其显式。

2. 为什么需要explicit

隐式转换在方便的同时会引发难以发现的逻辑错误、二义性或意外重载匹配,explicit 能把“自动发生”的转换变成“必须写成 T(x)static_cast<T>(x) 的显式转换”,提高代码可读性与安全性。

3.explicit用法示例

3.1 对构造函数

struct A {
    explicit A(int x) : v(x) {}
    int v;
};
void foo(A a) {}
foo(10);         // 错误:A(int) 为 explicit,禁止隐式转换
foo(A(10));      // 正确(显式)
foo(static_cast<A>(10)); // 正确

3.2 对转换运算符(conversion operator)

C++11 起可以写 explicit operator T() const,从而禁止隐式转换为 T

struct S {
    explicit operator bool() const { return true; }
};
S s;
if (s) { }  // 不能:implicit conversion to bool is not allowed?  
           // Actually `if (s)` requires context of boolean; for explicit operator bool, direct-initialization in if condition uses explicit? Explanation below.

说明:explicit operator bool() 的引入是为了替代“safe bool idiom”。explicit 转换运算符不会参与某些隐式转换场景,从而避免意外使用。标准对什么时候允许使用显式转换运算符(例如在 if (expr)static_cast<bool>(expr)、直接初始化等)做了具体规定。详见 cppreference

3.3 C++20 条件explicit(expr)

可以根据模板参数或常量条件使构造函数/转换运算符有条件地显式

template<typename T>
struct Wrapper {
    explicit(sizeof(T) > 4) Wrapper(T);   // C++20: 如果 T 大于4字节,则构造器为 explicit
};

或对转换运算符:

struct X {
    explicit(sizeof(int) <= 4) operator int() const;
};

这种写法让模板库可以更精细控制隐式转换行为。

4. 显式转换运算符的语义细节

  • 何时可以用显式转换运算符用于上下文? 标准允许在某些上下文需要显式转换的场合使用 explicit 运算符(例如带有直接初始化的场合、显式类型转换 static_cast、以及某些语句/条件表达式),而在需要隐式转换的上下文中(如拷贝初始化)是不被自动使用的。cppreference 对这些规则有详细列出。
  • explicit operator bool()if (obj)if 语句要求 “contextually convertible to bool” —— 这允许显式的 operator bool() 被用于条件判断(即 if (obj) 会触发显式转换运算符)——这就是为什么 explicit operator bool() 成为 “safe bool” 的现代替代。更多细节见实践资源。

5. 常见误用与陷阱(explicit)

  • 把每个构造函数都写成 explicit 会减少便利性 —— 设计上应仅对单参数(或可被单参数调用的)构造函数考虑 explicit,以防意外隐式转换。
  • 对于 conversion operator,默认不要轻易设为 implicit(非 explicit),除非非常确定该类型需要自然地作为目标类型使用(例如 std::string::operator std::string_view() 的情况)。一般建议:prefers named conversion function(如 .to_string())而不是提供很多隐式 conversion operators。

6. 推荐实践(explicit)

  • 对单参数构造函数通常使用 explicit,除非希望类型在表达式中自动构造(例如某些数值/枚举封装,需权衡)。
  • 对用户自定义的转换运算符,先考虑 explicit,只在确实需要隐式转换时才允许隐式。
  • 使用 C++20 的 explicit(expr) 在模板库中做细粒度控制,而不是写很多重载/模板特化。

三、constexpr与explicit的交互要点

  • 可以写 constexpr explicit 构造函数(非常常见):
struct S {
    constexpr explicit S(int x): v(x) {}
    int v;
};
constexpr S s = S(3); // ok: explicit but direct-initialization
  • 也可以写 constexpr explicit operator T()(C++11 起),该转化可在编译期用于常量表达式求值(只要上下文允许)。

四、实战样例(综合示例)

#include <type_traits>
// C++20 风格:条件 explicit + constexpr + consteval 示范
struct Big {
    int x;
    constexpr explicit Big(int v) : x(v) {}  // constexpr + explicit 构造器
    explicit operator int() const { return x; } // explicit conversion operator
};
consteval int must_be_ctime() { return 42; }  // 强制编译期求值 (C++20)
constinit int g = must_be_ctime();            // 强制常量初始化 (C++20)
static_assert(std::is_same_v<decltype(static_cast<int>(Big(3))), int>); // OK
// auto a = Big(3); // if function takes Big by value, implicit construction not allowed

五、常见问题快速问答

Q:constexpr 函数内部能用循环/分支吗?
A:可以(从 C++14 起放宽),但用于编译期时函数体中的所有操作必须可在编译期执行(不能进行未允许的运行时操作)。

Q:constevalconstexpr 的区别?
A:consteval 强制编译期求值;constexpr 允许编译期也允许运行期调用。

Q:explicit operator 什么时候应该使用
A:当你想阻止类型被无意间隐式转换(尤其是布尔/数值上下文)时应使用 explicit;否则可能引发模糊或错误的重载选择。

六、参考资料

  • constexpr specifier (since C++11) — cppreference(详述 constexpr 演进与限制)。
  • Conversion function (operator type) — cppreference(说明 explicit operator 的语法与语义,及 C++20 条件 explicit)。
  • const vs constexpr vs consteval vs constinit(总结与对比,含 C++20 新关键字)。

到此这篇关于C++中constexpr 与 explicit关键字使用实战样例的文章就介绍到这了,更多相关C++ constexpr 与 explicit使用内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Qt无边框窗口拖拽和阴影的实现

    Qt无边框窗口拖拽和阴影的实现

    自定义窗口控件的无边框,窗口事件由于没有系统自带边框,无法实现拖拽拉伸等事件的处理,本文主要介绍了Qt无边框窗口拖拽和阴影的实现,感兴趣的可以了解一下
    2024-01-01
  • C++中const应放在类型前还是后

    C++中const应放在类型前还是后

    之前遇到小伙伴问C++中const加在类型名前和变量名前的区别,今天给大家简单分析下。
    2016-05-05
  • 快速入门的一些C\C++书籍

    快速入门的一些C\C++书籍

    这篇文章为大家精心推荐了一些快速入门的一些C\C++书籍,希望大家可以喜欢,对这门语言可以产生兴趣,需要的朋友可以参考下
    2015-12-12
  • 基于malloc与free函数的实现代码及分析

    基于malloc与free函数的实现代码及分析

    本篇文章介绍了malloc与free函数的实现代码及分析。需要的朋友参考下
    2013-05-05
  • C与C++中结构体的区别

    C与C++中结构体的区别

    C中的结构体只涉及到数据结构,而不涉及到算法,也就是说在C中数据结构和算法是分离的,而到C++中一类或者一个结构体可以包含函数(这个函数在C++我们通常中称为成员函数),C++中的结构体和类体现了数据结构和算法的结合
    2013-10-10
  • C++ 异常处理noexcept正确使用示例详解

    C++ 异常处理noexcept正确使用示例详解

    这篇文章主要为大家介绍了C++ 异常处理noexcept正确使用示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-04-04
  • VC实现图片拖拽及动画的实例

    VC实现图片拖拽及动画的实例

    这篇文章介绍了VC实现图片拖拽及动画的实例,有需要的朋友可以参考一下
    2013-08-08
  • 基于C++实现职工管理系统

    基于C++实现职工管理系统

    这篇文章主要为大家详细介绍了基于C++实现职工管理系统,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-06-06
  • C++小知识:复制粘贴代码千万要小心

    C++小知识:复制粘贴代码千万要小心

    今天小编就为大家分享一篇关于C++小知识:复制粘贴代码千万要小心,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2019-01-01
  • C语言实现一个简单的扫雷游戏

    C语言实现一个简单的扫雷游戏

    扫雷是电脑上很经典的游戏,特意去网上玩了一会,几次调试之后,发现这个比三子棋要复杂一些,尤其是空白展开算法上和堵截玩家有的一拼,与实际游戏差别较大,不能使用光标,下面来详解每一步分析
    2021-10-10

最新评论