相关内容参考:C++中constexpr 与 explicit关键字使用详解
1.constexpr——“让编译器做更多事”
①提出动机:提升性能,减少运行时开销
在 C++11 之前:
- 只有
const,但const 不保证编译期求值 - 想要编译期常量,只能用
#define或enum——都不够安全、不支持函数、不支持复杂类型
因此提出constexpr解决两个痛点:
痛点1:需要真正的编译期常量(strong compile-time constant)
例如:
constintN=foo(10);// 不能保证 foo(10) 在编译期运行intarr[N];// 可能无法作为数组大小C++ 希望:如果值能在编译期确定,就应该在编译期确定。
痛点2:需要编译期可执行的函数(constexpr function)
例如:
doublearea(doubler){return3.14159*r*r;}constexprdoubleA=area(10);// C++03 之前不行C++ 希望:
- 数学函数
- 小工具函数
- 初始化对象
- 甚至类构造函数
都能在编译期执行。
②constexpr给 C++ 带来的核心能力
编译期执行函数(Compile-time evaluation)
极大提高运行效率 + 常量折叠 + 去掉运行时开销。
更强的类型安全
比#define可靠得多。
用于模板元编程(metaprogramming)
constexpr让模板元编程更直观,不必写复杂的 TMP 结构体推导。
允许类的编译期构造
例如:
structVec3{doublex,y,z;constexprVec3(doublex,doubley,doublez):x(x),y(y),z(z){}};③ 总结——constexpr 提出的根本动机
让编译器“成为运行时”,能提前计算任何能计算的东西,提高性能与安全性,增强 C++ 的表达能力。
一句话:
constexpr = 让 C++ 从运行时语言 → 编译期可计算语言
2.explicit——“拒绝隐式转换导致的灾难”
①提出动机:防止隐式类型转换引发 bug
在 C++98 之前:
构造函数可以隐式调用,导致非常多的意外错误:
structVec{Vec(intx){}// 可隐式转换};Vec v=5;// ??? 编译器会自动调用 Vec(5)问题:
- 难以察觉的隐式转换
- 编译器默默干了不想要的事
- 操作符重载时尤其危险
如:
booloperator==(constVec&,constVec&);Vec v;boolt=(v==10);// 会被隐式转成 Vec(10)这是非常危险的行为。
② explicit 的目标:禁止不安全的隐式构造
explicitVec(intx);禁止:
Vec v=5;// 不再允许保留:
Vecv(5);// 明确构造③ explicit 的更高层动机(C++11 之后)
C++11 支持:
explicit用于转换运算符explicit(false)/explicit(true)(C++20)
用于禁止 “过度智能” 的隐式行为。
例如:
explicitoperatorbool()const;防止:
Vector v;if(v){}//intx=v+1;// 防止自动转 bool 再转 int 的怪行为④ 总结——explicit 提出的根本动机
explicit 的目的就是阻止编译器做你没说过的隐式转换,避免隐式构造引发隐蔽 bug。
一句话:
explicit = 禁止偷偷摸摸的隐式转换,保护你不被语言陷阱坑死。
对比总结
| 关键字 | 核心动机 | 解决的问题 |
|---|---|---|
| constexpr | 让编译期可以计算更多东西,提高性能与安全性 | 编译期常量、constexpr 函数、constexpr 构造函数 |
| explicit | 禁止隐式转换,减少意料之外的 bug | 防止构造函数隐式调用,防止隐式转换 |
3constvsconstexpr对比
C++ 中const和constexpr都与常量相关,但用途和能力差异非常大:
| 特性 | const | constexpr |
|---|---|---|
| 定义 | 声明一个值不可修改 | 声明一个值或函数可以在编译期求值 |
| 是否保证编译期 | 不保证,可能运行时初始化 | 保证,如果满足条件,必须在编译期计算 |
| 适用对象 | 变量、成员、函数返回值 | 变量、成员、函数、构造函数、类常量 |
| 初始化 | 可以运行时初始化 | 必须用编译期常量初始化(C++14 起 relax,可有简单运行时计算) |
| 函数 | const函数限制成员修改(方法级) | constexpr函数在编译期求值,并且可用于常量表达式 |
| 数组长度 | const int N = 10; int arr[N];编译器可能支持,但不保证 | constexpr int N = 10; int arr[N];必定可用于编译期大小 |
| 优化 | 运行时常量,编译器可优化 | 编译期常量,可完全消除运行时开销 |
举例
constinta=5;// 运行时可变对象也可初始化为常量intarr1[a];// 在一些编译器中可行,但非标准保证constexprintb=5;// 编译期常量intarr2[b];// 标准保证可用函数对比
constintsquare_const(intx){returnx*x;}// 运行时求值constexprintsquare_constexpr(intx){returnx*x;}// 编译期求值可能square_constexpr(3)→ 编译期可求值square_const(3)→ 编译期不保证,只能运行时求值
4explicit在模板类 / 复杂构造中的用法
explicit最初是为了防止隐式类型转换,但在模板类 / 泛型编程中尤其重要:
① 模板类单参数构造
template<typenameT>structWrapper{explicitWrapper(T val):value(val){}T value;};Wrapper<int>w1(5);// 明确构造Wrapper<int>w2=5;// 错误,禁止隐式转换在模板类中,如果不加
explicit,可能会导致意想不到的隐式类型转换,尤其当模板参数为复杂类型(比如矩阵、点云类型)时。
② 多参数模板构造 + 默认参数
template<typenameT>structPoint{explicitPoint(T x=0,T y=0,T z=0):x(x),y(y),z(z){}T x,y,z;};Point<int>p1(1,2,3);// 明确构造Point<int>p2;// 默认参数构造Point<int>p3={};// 禁止隐式列表初始化加
explicit可以避免模板类在列表初始化 / 隐式转换时产生不安全行为。
③explicit与转换运算符(C++11+)
C++11 引入了explicit operator,用于模板类中的类型安全转换:
template<typenameT>structVec{T x,y;explicitoperatorbool()const{returnx!=0||y!=0;}};Vec<int>v{0,0};if(v){}// 允许intn=v;// 禁止隐式转换- 模板类中尤其重要,因为模板类型可能变化,不加
explicit很容易产生隐式转换错误。
④ 小结
const vs constexpr
const= 不可修改(可能运行时求值)constexpr= 编译期求值,可用于模板/数组/常量初始化
explicit 在模板类中
- 防止单参数模板构造产生隐式类型转换
- 避免列表初始化 / 默认参数带来的隐式调用
- 可用于转换运算符,增加类型安全性