constexpr 和 explicit 在 C++ 中被提出的动机

相关内容参考:C++中constexpr 与 explicit关键字使用详解


1. constexpr ------"让编译器做更多事"

提出动机:提升性能,减少运行时开销

在 C++11 之前:

  • 只有 const,但 const 不保证编译期求值
  • 想要编译期常量,只能用 #defineenum------都不够安全、不支持函数、不支持复杂类型

因此提出 constexpr 解决两个痛点:

痛点1:需要真正的编译期常量(strong compile-time constant)

例如:

cpp 复制代码
const int N = foo(10);  // 不能保证 foo(10) 在编译期运行
int arr[N];             // 可能无法作为数组大小

C++ 希望:如果值能在编译期确定,就应该在编译期确定。


痛点2:需要编译期可执行的函数(constexpr function)

例如:

cpp 复制代码
double area(double r) { return 3.14159 * r * r; }
constexpr double A = area(10);   // C++03 之前不行

C++ 希望:

  • 数学函数
  • 小工具函数
  • 初始化对象
  • 甚至类构造函数

都能在编译期执行。


constexpr 给 C++ 带来的核心能力

编译期执行函数(Compile-time evaluation)

极大提高运行效率 + 常量折叠 + 去掉运行时开销。

更强的类型安全

#define 可靠得多。

用于模板元编程(metaprogramming)

constexpr 让模板元编程更直观,不必写复杂的 TMP 结构体推导。

允许类的编译期构造

例如:

cpp 复制代码
struct Vec3 {
    double x, y, z;
    constexpr Vec3(double x, double y, double z) : x(x), y(y), z(z) {}
};

③ 总结------constexpr 提出的根本动机

让编译器"成为运行时",能提前计算任何能计算的东西,提高性能与安全性,增强 C++ 的表达能力。

一句话:

constexpr = 让 C++ 从运行时语言 → 编译期可计算语言


2. explicit ------"拒绝隐式转换导致的灾难"

提出动机:防止隐式类型转换引发 bug

在 C++98 之前:

构造函数可以隐式调用,导致非常多的意外错误:

cpp 复制代码
struct Vec {
    Vec(int x) {}  // 可隐式转换
};

Vec v = 5;  // ??? 编译器会自动调用 Vec(5)

问题:

  • 难以察觉的隐式转换
  • 编译器默默干了不想要的事
  • 操作符重载时尤其危险

如:

cpp 复制代码
bool operator==(const Vec&, const Vec&);
Vec v;
bool t = (v == 10); // 会被隐式转成 Vec(10)

这是非常危险的行为。


② explicit 的目标:禁止不安全的隐式构造

cpp 复制代码
explicit Vec(int x);

禁止:

cpp 复制代码
Vec v = 5;   //  不再允许

保留:

cpp 复制代码
Vec v(5);    //  明确构造

③ explicit 的更高层动机(C++11 之后)

C++11 支持:

  • explicit 用于转换运算符
  • explicit(false) / explicit(true)(C++20)

用于禁止 "过度智能" 的隐式行为。

例如:

cpp 复制代码
explicit operator bool() const;

防止:

cpp 复制代码
Vector v;
if(v) {}        //
int x = v + 1;  //  防止自动转 bool 再转 int 的怪行为

④ 总结------explicit 提出的根本动机

explicit 的目的就是阻止编译器做你没说过的隐式转换,避免隐式构造引发隐蔽 bug。

一句话:

explicit = 禁止偷偷摸摸的隐式转换,保护你不被语言陷阱坑死。


对比总结

关键字 核心动机 解决的问题
constexpr 让编译期可以计算更多东西,提高性能与安全性 编译期常量、constexpr 函数、constexpr 构造函数
explicit 禁止隐式转换,减少意料之外的 bug 防止构造函数隐式调用,防止隐式转换

3 const vs constexpr 对比

C++ 中 constconstexpr 都与常量相关,但用途和能力差异非常大:

特性 const constexpr
定义 声明一个值不可修改 声明一个值或函数可以在编译期求值
是否保证编译期 不保证,可能运行时初始化 保证,如果满足条件,必须在编译期计算
适用对象 变量、成员、函数返回值 变量、成员、函数、构造函数、类常量
初始化 可以运行时初始化 必须用编译期常量初始化(C++14 起 relax,可有简单运行时计算)
函数 const 函数限制成员修改(方法级) constexpr 函数在编译期求值,并且可用于常量表达式
数组长度 const int N = 10; int arr[N]; 编译器可能支持,但不保证 constexpr int N = 10; int arr[N]; 必定可用于编译期大小
优化 运行时常量,编译器可优化 编译期常量,可完全消除运行时开销

举例

cpp 复制代码
const int a = 5;       // 运行时可变对象也可初始化为常量
int arr1[a];            // 在一些编译器中可行,但非标准保证

constexpr int b = 5;   // 编译期常量
int arr2[b];            // 标准保证可用

函数对比

cpp 复制代码
const int square_const(int x) { return x*x; }      // 运行时求值
constexpr int square_constexpr(int x) { return x*x; } // 编译期求值可能
  • square_constexpr(3) → 编译期可求值
  • square_const(3) → 编译期不保证,只能运行时求值

4 explicit 在模板类 / 复杂构造中的用法

explicit 最初是为了防止隐式类型转换,但在 模板类 / 泛型编程 中尤其重要:


① 模板类单参数构造

cpp 复制代码
template<typename T>
struct Wrapper {
    explicit Wrapper(T val) : value(val) {}
    T value;
};

Wrapper<int> w1(5);   //  明确构造
Wrapper<int> w2 = 5;  //  错误,禁止隐式转换

在模板类中,如果不加 explicit,可能会导致意想不到的隐式类型转换,尤其当模板参数为复杂类型(比如矩阵、点云类型)时。


② 多参数模板构造 + 默认参数

cpp 复制代码
template<typename T>
struct Point {
    explicit Point(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,用于模板类中的类型安全转换:

cpp 复制代码
template<typename T>
struct Vec {
    T x, y;
    explicit operator bool() const { return x != 0 || y != 0; }
};

Vec<int> v{0,0};
if (v) { }  //  允许
int n = v;  //  禁止隐式转换
  • 模板类中尤其重要 ,因为模板类型可能变化,不加 explicit 很容易产生隐式转换错误。

④ 小结

  1. const vs constexpr

    • const = 不可修改(可能运行时求值)
    • constexpr = 编译期求值,可用于模板/数组/常量初始化
  2. explicit 在模板类中

    • 防止单参数模板构造产生隐式类型转换
    • 避免列表初始化 / 默认参数带来的隐式调用
    • 可用于转换运算符,增加类型安全性

相关推荐
White_Can8 小时前
《C++11:智能指针》
c++·c++11·智能指针
无限进步_8 小时前
【数据结构&C语言】对称二叉树的递归之美:镜像世界的探索
c语言·开发语言·数据结构·c++·算法·github·visual studio
im_AMBER8 小时前
Leetcode 98 从链表中移除在数组中存在的节点
c++·笔记·学习·算法·leetcode·链表
CSDN_RTKLIB8 小时前
C++取模与取余
开发语言·c++
星河耀银海8 小时前
C++开发入门——环境搭建与第一个程序
开发语言·c++·策略模式
还不秃顶的计科生9 小时前
defaultdict讲解
开发语言·javascript·ecmascript
花归去9 小时前
echarts 柱状图包含右侧进度
开发语言·前端·javascript
wjs20249 小时前
Java 数组
开发语言
码农水水9 小时前
大疆Java面试被问:TCC事务的悬挂、空回滚问题解决方案
java·开发语言·人工智能·面试·职场和发展·单元测试·php
qq_2518364579 小时前
基于java Web 个人网站系统设计与实现
java·开发语言·数据库