C++新特性整理目录:
- C++11新特性(总结)
- C++14新特性(总结)
- C++17新特性(总结)
- C++20新特性(总结)
- C++23新特性(总结)
- C++新特性对比说明(11、14、17、20、23)
- C++新特性详细对比表(11、14、17、20、23)
在我们学习一个新的语言特性的时候,我们应该带着这样几个问题去学习:为什么要使用它,它能给我们的程序带来什么好处、能够解决在以往编程过程中的什么问题?那么现在让我们带着这样几个问题,来学习一下constexpr吧:为什么要使用constexpr?使用constexpr能给我们的程序带来什么好处?能够解决在以往编程过程中的什么问题?
概述
constexpr是C++从11标准开始引入的核心特性,它代表"常量表达式",旨在将计算从运行时转移到编译时,实现真正的零开销抽象。其核心思想是:允许编译器在编译期间执行函数和对象初始化,从而消除运行时开销,同时增强代码安全性。
演进历程展现了C++对编译时计算能力的持续强化。C++11中,constexpr函数只能包含单条return语句,主要用于基础数学计算。C++14解除了函数体限制,允许循环、分支和局部变量,使复杂算法得以编译时执行。C++17引入if constexpr实现编译时分支,Lambda默认支持constexpr。C++20实现了质的飞跃:允许动态内存分配、标准容器(vector/string)和虚函数在编译时使用,几乎实现了"编译时执行任何运行时能执行的代码"。
核心价值体现在三个层面:性能上,将已知数据的计算提前到编译时,消除运行时计算开销;安全上,通过static_assert在编译时验证业务逻辑,提前发现错误;表达力上,用直观的普通C++语法替代晦涩的模板元编程。在嵌入式系统、游戏开发、高性能计算等领域,constexpr使得配置解析、数学运算、数据结构初始化等操作能够在编译时完成,既保证了性能又增强了安全性。
本质上,constexpr让C++程序员能够用同一套代码表达编译时和运行时逻辑,编译器根据上下文自动选择最佳执行时机,这是对"零开销抽象"哲学的最好实践。
一、constexpr 在 C++ 各版本中的发展
C++11:初代引入
解决的问题:
- 编译时常量表达式的严格定义:明确区分编译时可计算和运行时计算
- 替代部分模板元编程:简化编译时计算
- 数组大小和模板参数的动态计算:允许使用函数结果作为编译时常量
核心限制:
- 函数体只能包含单个
return语句- 不能有局部变量、循环、分支等
- 所有参数必须是字面量类型
- 返回类型必须是字面量类型
使用示例:
cpp
// 简单函数(只有一条return语句)
constexpr int square(int x) {
return x * x; // 只能有一条语句
}
constexpr int factorial(int n) {
return (n <= 1) ? 1 : n * factorial(n - 1); // 递归,但不能有if语句
}
// 模板参数使用
template<int N>
struct Array {
int data[N];
};
int main()
{
// 基础变量声明
constexpr int size = 10;
constexpr double pi = 3.141592653589793;
// 编译时计算
constexpr int arr_size = square(5); // 25
int arr25[arr_size]; // 数组大小 = 25
// 类型定义
struct Point {
constexpr Point(double x, double y) : x(x), y(y) {}
double x, y;
};
constexpr Point origin(0.0, 0.0);
constexpr Point unit(1.0, 0.0);
Array<factorial(4)> arr24; // Array<24>
}
局限性示例:
cpp
// 以下代码在C++11中无效:
constexpr int sum_to_n(int n) {
int result = 0; // 错误:局部变量
for (int i = 1; i <= n; ++i) { // 错误:循环
result += i;
}
return result;
}
C++14:重大放松
解决的问题:
- 函数体限制过多:允许更自然的函数写法
- 编译时计算能力有限:支持更复杂的算法
- 实用性不足:难以编写实际有用的constexpr函数
核心改进:
- 允许局部变量(非
static、非thread_local)- 允许
if、switch、for、while等控制流- 允许修改函数内的局部变量
- 允许
void返回类型
使用示例:
cpp
// 复杂的编译时函数
constexpr int fibonacci(int n) {
if (n <= 1) return n;
int a = 0, b = 1; // C++14允许局部变量
for (int i = 2; i <= n; ++i) { // C++14允许循环
int temp = a + b;
a = b;
b = temp;
}
return b;
}
// 编译时字符串处理
constexpr int string_length(const char* str) {
int length = 0;
while (str[length] != '\0') {
++length;
}
return length;
}
// 编译时查找
constexpr int find(const int* arr, int size, int target) {
for (int i = 0; i < size; ++i) {
if (arr[i] == target) {
return i;
}
}
return -1;
}
// 更复杂的数学计算
constexpr double power(double base, int exp) {
if (exp == 0) return 1.0;
double result = 1.0;
int abs_exp = exp > 0 ? exp : -exp;
for (int i = 0; i < abs_exp; ++i) {
result *= base;
}
return exp > 0 ? result : 1.0 / result;
}
int main()
{
constexpr int fib10 = fibonacci(10); // 55
constexpr auto len = string_length("hello"); // 5
constexpr int arr[] = { 1, 2, 3, 4, 5 };
constexpr int pos = find(arr, 5, 3); // 2
}
C++17:编译时编程的飞跃
解决的问题:
- 编译时分支选择不够优雅 :提供
if constexpr- Lambda表达式限制:支持constexpr lambda
- 编译时静态多态:简化模板元编程
核心改进:
if constexpr:编译时条件分支- Lambda表达式默认
constexprconstexpr静态成员变量不再需要类外定义- 更多标准库组件支持
constexpr
使用示例:
cpp
// if constexpr:编译时分支
template<typename T>
constexpr auto get_value(T t) {
if constexpr (std::is_pointer_v<T>) {
return *t; // 编译时决定是否生成这段代码
}
else if constexpr (std::is_integral_v<T>) {
return t + 1;
}
else {
return t;
}
}
// 编译时类型分发
template<typename... Ts>
constexpr auto sum_all(Ts... args) {
if constexpr (sizeof...(args) == 0) {
return 0;
}
else {
return (args + ...); // 折叠表达式
}
}
// constexpr lambda(C++17起)
constexpr auto square_lambda = [](int x) constexpr {
return x * x;
};
// 编译时lambda调用
constexpr int result = [](int n) constexpr {
int sum = 0;
for (int i = 1; i <= n; ++i) sum += i;
return sum;
}(100); // 编译时计算1到100的和
// 编译时字符串处理增强
constexpr bool starts_with(std::string_view str, std::string_view prefix) {
if (str.length() < prefix.length()) return false;
for (size_t i = 0; i < prefix.length(); ++i) {
if (str[i] != prefix[i]) return false;
}
return true;
}
// 编译时验证
template<size_t N>
struct FixedString {
constexpr FixedString(const char(&str)[N]) {
for (size_t i = 0; i < N; ++i) {
data[i] = str[i];
}
}
char data[N];
};
// 使用if constexpr实现编译时算法选择
template<typename Iterator>
constexpr void sort(Iterator begin, Iterator end) {
using value_type = typename std::iterator_traits<Iterator>::value_type;
if constexpr (std::is_integral_v<value_type>) {
// 对整数使用快速排序(编译时选择)
quick_sort(begin, end);
}
else {
// 其他类型使用插入排序
insertion_sort(begin, end);
}
}
// 静态成员变量简化
struct MathConstants {
static constexpr double pi = 3.141592653589793;
static constexpr double e = 2.718281828459045;
// C++17:不需要类外定义
};
// 编译时数组操作
constexpr auto create_lookup_table() {
std::array<int, 10> table = {}; // C++14std::array构造函数还不是constexpr,C++17支持了
for (int i = 0; i < 10; ++i) {
table[i] = i * i; // C++14允许修改局部变量
}
return table;
}
int main()
{
constexpr auto val1 = get_value(42); // 43
constexpr auto val2 = get_value(&val1); // 42
constexpr auto total = sum_all(1, 2, 3, 4); // 10
static_assert(starts_with("hello world", "hello"));
static_assert(square_lambda(5) == 25);
constexpr auto squares = create_lookup_table();
static_assert(squares[3] == 9, "3^2 should be 9");
}
C++20:编译时编程的全面能力
解决的问题:
- 动态内存分配不能用于编译时 :允许
new/delete在constexpr中- 异常处理限制 :允许
try-catch在constexpr中核心改进
- 允许
new和delete(编译时分配,编译时释放)- 允许
try-catch(但catch块不能执行)- 允许类型转换(
dynamic_cast、typeid)- 允许
asm声明(但不能执行)
使用示例:
(1)动态内存分配(编译时 new/delete)
cpp
//编译时动态数组
constexpr auto create_dynamic_array(int size) {
// C++20允许编译时使用new和delete
int* arr = new int[size];
// 初始化数组
for (int i = 0; i < size; ++i) {
arr[i] = i * i; // 平方数
}
// 计算总和
int sum = 0;
for (int i = 0; i < size; ++i) {
sum += arr[i];
}
// 编译时必须释放内存!
delete[] arr;
return sum;
}
// 编译时内存管理规则
constexpr auto memory_demo() {
// 可以分配,但必须在同一constexpr上下文中释放
int* p1 = new int(42);
int* p2 = new int[5] {1, 2, 3, 4, 5};
// 编译时修改动态内存
p2[0] = 100;
int result = *p1 + p2[0];
// 必须成对释放
delete p1;
delete[] p2;
return result;
}
int main()
{
constexpr int dynamic_sum = create_dynamic_array(10);
static_assert(dynamic_sum == 285); // 0²+1²+...+9² = 285
constexpr int mem_result = memory_demo(); // 142
}
如果我们返回new的指针会怎么样?示例代码如下:
cpp
constexpr auto memory_demo() {
int* p2 = new int[5] {1, 2, 3, 4, 5};
return p2;
}
int main()
{
constexpr int mem_result = memory_demo(); // 142
}
编译试试:

这说明:可以分配,但必须在同一constexpr上下文中释放
(2)异常处理支持【编译时try-catch】
cpp
// 编译时try-catch
constexpr int safe_divide(int a, int b) {
try {
if (b == 0) {
throw std::runtime_error("Division by zero");
}
return a / b;
}
catch (const std::exception& e) {
// 注意:catch块在编译时执行时必须是常量表达式
// 但我们可以返回一个默认值
return 0;
}
}
// 更复杂的异常处理
constexpr auto parse_number(std::string_view str) -> std::optional<int> {
try {
// 尝试解析字符串为整数
int value = 0;
for (char c : str) {
if (c < '0' || c > '9') {
throw std::invalid_argument("Not a number");
}
value = value * 10 + (c - '0');
}
return value;
}
catch (...) {
return std::nullopt;
}
}
constexpr auto memory_demo() {
int* p2 = new int[5] {1, 2, 3, 4, 5};
return p2;
}
int main()
{
constexpr int result1 = safe_divide(10, 2); // 5
constexpr int result2 = safe_divide(10, 0); // 0(编译时捕获异常)
constexpr auto num1 = parse_number("123");
constexpr auto num2 = parse_number("12a"); // 返回nullopt
static_assert(num1.value() == 123);
static_assert(num2.has_value());
static_assert(num1.has_value());
}
针对抛出异常的情况,编译器将无法编译通过
(3)编译时多态说到多态我们就会想到虚函数,那么C++20是通过编译时虚函数调用来支持编译时多态的吗?我们先来看看这样一段代码:
cpp
struct Shape {
constexpr virtual double area() const = 0;
constexpr virtual std::string_view name() const = 0;
constexpr virtual ~Shape() = default; // 虚析构函数也可以constexpr
};
struct Circle : Shape {
constexpr Circle(double r) : radius(r) {}
constexpr double area() const override {
return 3.141592653589793 * radius * radius;
}
constexpr std::string_view name() const override {
return "Circle";
}
double radius;
};
struct Rectangle : Shape {
constexpr Rectangle(double w, double h) : width(w), height(h) {}
constexpr double area() const override {
return width * height;
}
constexpr std::string_view name() const override {
return "Rectangle";
}
double width, height;
};
constexpr double total_area(const Shape* const* shapes, size_t count) {
double total = 0.0;
for (size_t i = 0; i < count; ++i) {
total += shapes[i]->area(); // 编译时虚函数调用!
}
return total;
}
int main()
{
// 使用示例
constexpr Circle circle(5.0);
constexpr Rectangle rect(4.0, 6.0);
constexpr const Shape* shapes[] = { &circle, &rect };
constexpr double total = total_area(shapes, 2);
static_assert(total > 78.0 && total < 114.0); // 圆面积≈78.54 + 矩形面积24
}
很遗憾,编译无法通过,问题出在这样一段代码:
constexpr const Shape* shapes[] = { &circle, &rect };
这可能是取地址问题,我们对代码做如下修改,看看会怎样?
cpp
...
constexpr double area()
{
Circle *circle = new Circle(5.0);
Rectangle* rect = new Rectangle(4.0, 6.0);
double total = total_area(shapes, 2);
delete circle;
delete rect;
return total;
}
int main()
{
constexpr double total = area();
static_assert(total > 78.0 && total < 114.0); // 圆面积≈78.54 + 矩形面积24
}
还是无法编译通过,这次问题在double total = total_area(shapes, 2);我们在改改area()函数看看
cpp
constexpr double area()
{
Circle *circle = new Circle(5.0);
Rectangle* rect = new Rectangle(4.0, 6.0);
double total = circle->area()+ rect->area();
delete circle;
delete rect;
return total;
}
依旧无法编译通过,我们将area改成普通成员函数呢?
cpp
struct Shape {
//constexpr virtual double area() const = 0;
constexpr virtual std::string_view name() const = 0;
constexpr virtual ~Shape() = default; // 虚析构函数也可以constexpr
};
struct Circle : Shape {
constexpr Circle(double r) : radius(r) {}
constexpr double area() const {
return 3.141592653589793 * radius * radius;
}
constexpr std::string_view name() const override {
return "Circle";
}
double radius;
};
struct Rectangle : Shape {
constexpr Rectangle(double w, double h) : width(w), height(h) {}
constexpr double area() const {
return width * height;
}
constexpr std::string_view name() const override {
return "Rectangle";
}
double width, height;
};
constexpr double area()
{
Circle *circle = new Circle(5.0);
Rectangle* rect = new Rectangle(4.0, 6.0);
double total = circle->area()+ rect->area();
delete circle;
delete rect;
return total;
}
int main()
{
constexpr double total = area();
//static_assert(total > 78.0 && total < 114.0); // 圆面积≈78.54 + 矩形面积24
}
没有问题,这说明还是编译时虚函数调用问题。那么如何实现编译时多态呢?
我们可以这样理解:
编译期多态是C++中一种在编译阶段实现多态性的技术,主要通过模板和函数重载来实现。与运行期多态不同,编译期多态不依赖虚函数表,而是通过模板实例化和编译器的静态分析来确定调用的具体函数。这种多态性也被称为静态多态。
我们继续看看下面这样一段代码:
cpp
#include <iostream>
using namespace std;
class Dog {
public:
void shout() { cout << "汪汪!" << endl; }
};
class Cat {
public:
void shout() { cout << "喵喵~" << endl; }
};
template <typename T>
void animalShout(T& animal) {
animal.shout();
}
int main() {
Dog dog;
Cat cat;
animalShout(dog); // 输出: 汪汪!
animalShout(cat); // 输出: 喵喵~
return 0;
}
我们再来看看像这样一段代码:
cpp
template<typename Derived>
class Base {
public:
constexpr Base() {};
constexpr double interface() {
return static_cast<Derived*>(this)->implementation();
}
double _val = 1.0;
};
class Derived1 : public Base<Derived1> {
public:
constexpr Derived1(double v) { _val = v; };
constexpr double implementation()
{
/* 实现1 */
return _val/2.0;
}
};
class Derived2 : public Base<Derived2> {
public:
constexpr Derived2(double v) { _val = v; };
constexpr double implementation()
{
/* 实现2 */
return _val+1.0;
}
};
template<typename T>
constexpr double implementation_t(T* b)
{
return b->interface();
}
constexpr double implementation()
{
Derived1 *d1 = new Derived1(6.0);
Derived2 *d2 = new Derived2(2.5);
double vd1 = implementation_t(d1);
double vd2 = implementation_t(d2);
delete d1;
delete d2;
return vd1 + vd2;
}
int main()
{
constexpr double v = implementation();
}
可以看出在哪一步体现出来了类似多态的效果了吗?
C++20的概念可以用来约束模板参数,从而实现更安全和更灵活的编译时多态性。例如,我们可以定义一个Animal概念,该概念要求一个类型必须有一个shout成员函数。然后,我们可以定义一个泛型函数,该函数接受一个Animal对象:
cpp
#include <iostream>
using namespace std;
class Dog {
public:
void shout() { cout << "汪汪!" << endl; }
};
class Cat {
public:
void shout() { cout << "喵喵~" << endl; }
};
class Mouse {
public:
//void shout() { cout << "喵喵~" << endl; }
};
template<typename T>
concept Animal = requires(T t) {
{ t.shout() } -> std::same_as<void>;
};
template<Animal T>
void animalShout(T& drawable) {
drawable.draw();
}
int main() {
Dog dog;
Cat cat;
animalShout(dog); // 输出: 汪汪!
animalShout(cat); // 输出: 喵喵~
//animalShout(Mouse); // 编译无法通过
return 0;
}
C++23:编译时编程的全面能力
cpp
#include <iostream>
using namespace std;
template<typename T>
struct Processor {
constexpr auto process(const T& value) const {
if consteval {
// 编译时优化版本
return compile_time_process(value);
}
else {
// 运行时版本
return runtime_process(value);
}
}
private:
constexpr auto compile_time_process(const T& value) const {
// 复杂的编译时计算
return value * value; // 示例
}
auto runtime_process(const T& value) const {
// 可能使用SIMD或其他运行时优化
return value * value;
}
};
int main() {
constexpr Processor<int> p;
constexpr auto ct_result = p.process(5); // 使用编译时路径
int x = 10;
auto rt_result = p.process(x); // 使用运行时路径
return 0;
}
二、主要解决的问题深度分析
constexpr的引入是C++发展史上的一个里程碑,它系统性解决了多个长期存在的痛点。让我们从实际编程问题出发,深入分析每个问题的背景和解决方案。
(1)解决"两套代码"问题:运行时与编译时逻辑分离问题表现:重复实现相同逻辑
问题根源:
- 相同算法需要两种实现
- 模板元编程语法晦涩,难以调试
- 维护成本翻倍,容易产生不一致
示例代码:
cpp
// C++03时代:必须维护两套代码
// 运行时版本
int runtime_fibonacci(int n) {
if (n <= 1) return n;
return runtime_fibonacci(n - 1) + runtime_fibonacci(n - 2);
}
// 编译时版本(模板元编程)
template<int N>
struct Fibonacci {
static const int value = Fibonacci<N - 1>::value + Fibonacci<N - 2>::value;
};
template<> struct Fibonacci<0> { static const int value = 0; };
template<> struct Fibonacci<1> { static const int value = 1; };
int main() {
// 使用
int rt_result = runtime_fibonacci(10); // 运行时计算
int ct_result = Fibonacci<10>::value; // 编译时计算
return 0;
}
cpp
// constexpr解决方案:统一实现
// C++11/14:一套代码,两种用途
constexpr int fibonacci(int n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
int main() {
// 使用
constexpr int ct_value = fibonacci(10); // 编译时计算
int user_input = 6;
int rt_value = fibonacci(user_input); // 运行时计算
int array[fibonacci(5)]; // 数组大小=5
return 0;
}
核心价值:DRY原则(Don't Repeat Yourself)的极致体现,减少代码重复和错误。
(2)解决"魔数"(Magic Numbers)和宏滥用问题问题表现:硬编码与宏的局限
示例代码:
cpp
// 传统方式:宏和硬编码
#define BUFFER_SIZE 1024
#define CACHE_LINE 64
#define MAX_RETRIES 3
class NetworkBuffer {
char buffer[BUFFER_SIZE]; // 数组大小
int alignment = CACHE_LINE; // 对齐值
// 问题:宏没有类型,没有作用域,难以调试
};
// 或者使用枚举
enum Constants {
BufferSize = 1024,
CacheLine = 64,
MaxRetries = 3
};
// 枚举的问题:类型是int,不能是其他类型(如double)
cpp
// constexpr解决方案:类型安全的常量表达式
// 现代C++:类型安全,可计算的常量
namespace constants {
constexpr size_t buffer_size = 1024;
constexpr size_t cache_line = 64;
constexpr int max_retries = 3;
constexpr double pi = 3.141592653589793;
// 甚至可以计算
constexpr size_t aligned_buffer_size(size_t size) {
return (size + cache_line - 1) & ~(cache_line - 1);
}
}
class NetworkBuffer {
char buffer[constants::buffer_size];
static constexpr int alignment = constants::cache_line;
char aligned_buffer[constants::aligned_buffer_size(512)];
};
核心价值:
- 类型安全,作用域明确
- 可计算,不只是字面量
- 支持浮点数等非整数类型
(3)解决模板参数限制问题问题表现:模板参数只能是编译时常量
示例代码
cpp
// C++03:模板参数必须是编译时已知的
template<int Size>
class FixedArray {
int data[Size];
public:
FixedArray() {}
};
int main() {
// 但如何获取Size?只能:
enum { MySize = 100 };
FixedArray<MySize> arr1; // 可以
int size = 5; // 运行时计算
//FixedArray<size> arr2; // 错误!size不是编译时常量
return 0;
}
cpp
// constexpr解决方案:函数作为编译时参数源
#include <array>
// C++11起:constexpr函数生成编译时常量
constexpr int calculate_buffer_size(int multiplier) {
return 64 * multiplier; // 可包含复杂逻辑
}
template<int Size>
class FixedArray {
std::array<int, Size> data;
public:
constexpr FixedArray() = default;
};
int main() {
// 使用
FixedArray<calculate_buffer_size(4)> arr1; // Size=256
FixedArray<calculate_buffer_size(10)> arr2; // Size=640
constexpr int precomputed_size = calculate_buffer_size(2);
FixedArray<precomputed_size> arr3; // Size=128
return 0;
}
核心价值:模板参数可以是函数计算结果,极大增强了模板的灵活性。
(4)解决编译时计算与运行时计算的性能矛盾问题表现:性能与灵活性的取舍
示例代码:
cpp
// 传统方式:要么静态表(浪费内存),要么运行时计算(浪费CPU)
// 选项1:预计算表(静态,浪费内存)
const int sin_table[360] = {
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, // ... 手工维护
};
// 选项2:运行时计算(每次调用都计算)
double runtime_sin(double angle) {
return sin(angle * 3.14159 / 180.0); // 每次都算
}
// 嵌入式场景特别痛苦:内存紧张但又需要性能
cpp
// constexpr解决方案:按需生成
#include <math.h>
#include <array>
// 现代C++:需要时才生成,最优平衡
constexpr double compile_time_sin(int degrees) {
// 使用泰勒展开等算法
double radians = degrees * 3.1415926535 / 180.0;
auto pow_m = [](int v, int s) ->int
{
int r = v;
for (int i = 1; i < s; i++)
{
r *= v;
}
return r;
};
return radians - pow_m(radians, 3) / 6 + pow_m(radians, 5) / 120;
//return radians - pow(radians, 3) / 6 + pow(radians, 5) / 120; //pow 函数可能还不支持
}
template<int... Degrees>
constexpr auto generate_sin_table() {
return std::array<double, sizeof...(Degrees)>{
compile_time_sin(Degrees)...
};
}
int main() {
// 使用:只生成需要的部分
constexpr auto sin_table = generate_sin_table<0, 15, 30, 45, 60, 75, 90>();
// 运行时仍然可以使用同一函数
int user_angle = 30;
double rt_value = compile_time_sin(user_angle);
return 0;
}
核心价值:
- 消除预计算表的维护成本
- 避免重复运行时计算
- 内存和CPU的最优平衡
(5)解决编译时验证的缺失问题问题表现:错误只能在运行时发现
cpp
// 传统方式:配置错误要到运行时才崩溃
struct Config {
int width;
int height;
int fps;
};
Config load_config() {
Config cfg;
cfg.width = 1920;
cfg.height = 1080;
cfg.fps = 120; // 错误:显示不支持120fps
// 验证逻辑
if (cfg.fps > 60) {
throw "Unsupported frame rate!"; // 运行时才抛出
}
return cfg;
}
// 用户可能在部署后才发现问题
cpp
// constexpr解决方案:编译时验证
// 现代C++:编译时就发现问题
struct Config {
int width;
int height;
int fps;
constexpr Config(int w, int h, int f)
: width(w), height(h), fps(f) {
// 编译时验证
if (fps > 60) {
throw "Unsupported frame rate!"; // 编译时错误
}
if (width <= 0 || height <= 0) {
throw "Invalid dimensions!"; // 编译时错误
}
}
};
// 使用
constexpr Config cfg(1920, 1080, 30); // OK
// constexpr Config bad_cfg(1920, 1080, 120); // 编译错误!
// 还可以结合static_assert
static_assert(Config(1920, 1080, 30).fps == 30, "Verification");
核心价值:
- 错误在编译期发现,成本接近于0
- 提供更强的API约束
- 适用于安全关键系统
根本哲学转变:从"写两套代码实现不同优化"到"写一套代码让编译器选择最佳执行时机"。一、consteval/constinit/constptr 对比分析
对比分析表:
| 特性 | 引入版本 | 用途 | 要求 | 适用对象 |
|---|---|---|---|---|
consteval |
C++20 | 创建立即函数,必须在编译期求值 | 函数体必须能在编译期执行 | 函数 |
constinit |
C++20 | 确保静态/线程局部变量的编译期初始化 | 初始值必须是常量表达式 | 静态/线程局部变量 |
constexpr |
C++11/14/17 | 函数/变量可以在编译期求值 | 比consteval宽松 |
函数、变量 |
consteval- 编译期强制求值
cpp
consteval int square(int x) { // 必须编译期求值
return x * x;
}
int main() {
constexpr int a = square(5); // OK
// int b = square(rand()); // 错误:运行时值
// consteval函数只能用于常量表达式
}
核心 :编译期执行的保证
不能:获取地址、有静态变量、有try-catch等
2.constinit- 编译期初始化保证
cpp
constexpr int get_value() { return 42; }
// 编译期初始化
constinit int global = get_value(); // 必须是常量表达式
// 错误:初始化值不是常量表达式
// constinit int bad = std::rand();
void func() {
// 错误:constinit只能用于静态存储期
// constinit int local = 10;
static constinit int static_var = 100; // OK
}
核心 :防止静态初始化顺序问题
必须:静态/线程存储期,常量表达式初始化
3.constexpr- 编译期求值能力(更宽松)
cpp
constexpr int factorial(int n) { // 可以编译期或运行期求值
return (n <= 1) ? 1 : n * factorial(n - 1);
}
int main() {
constexpr int a = factorial(5); // 编译期求值
int runtime = 10;
int b = factorial(runtime); // 运行期求值(允许)
}
通过这么多的讲解,相信大家对一开始的问题心中已经有了自己的答案了吧?
我的回答:为了在编译期执行原本只能在运行期进行的计算,解决编译时常量表达能力不足和性能优化受限的问题,从而实现对关键计算的零开销抽象、编译时验证和极致性能优化。
