现代C++的constexpr特性小结

 更新时间:2026年11月10日 10:31:01   作者:落羽的落羽  
本文从C++11到C++20全面梳理constexpr的演进,讲清常量表达式、constexpr函数、构造函数、lambda、虚函数等用法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

一、从C++11引入

1. 常量表达式和constexpr关键字的概念

现代C++,从C++11开始,引入了常量表达式和constexpr关键字的概念,并且在之后的C++标准中不断更新

常量表达式是指,值不会改变并且在编译期中就能得到计算结果的表达式。用字面量、常量表达式初始化的const对象都是常量表达式。但是用变量初始化的const对象不是常量表达式。

const int a = 1; //a是常量表达式
const int b = a + 1; //b是常量表达式
int c = 1; //c不是常量表达式
const int d = c; //d不是常量表达式,因为可能有代码影响c的值,编译时期无法确定c的值

由于常量表达式的值在编译时期就能确定,编译器在这里就能进行优化,在使用常量表达式的地方直接进行替换值,通过汇编语言我们能看出区别:

常量表达式的最大优点就是能将一些计算从运行时转移到编译时,提升程序运行时的性能。

C++11引入了一个关键字:constexpr,用于指定常量表达式,它允许在编译时计算表达式的值。
constexpr可以修饰变量,但修饰的变量一定要是常量表达式,且必须用常量或常量表达式进行初始化,否则会报错

constexpr也可以修饰指针,但修饰的是指针本身,也是就顶层const。

const可以分为顶层const、底层const。本身被const修饰叫做顶层const、指向的对象被const修饰叫做底层const。大多数普通对象被const修饰时都是顶层const;指针被const修饰时,*左边的const叫做底层const,*右边的const叫做顶层const;const修饰引用时,是底层const

2. constexpr修饰函数

constexpr可以修饰函数,constexpr函数默认都是inline的,函数的计算会由编译器在编译时期执行。但是,一个函数的逻辑可比表达式的逻辑复杂多了,这对于编译器的要求就更加严格,因此在C++11的标准中,对constexpr修饰的函数要求也十分严格:

  • 这个函数的参数和返回值必须都是字面类型(目前可以理解为包括整型、浮点型、指针、引用等)
  • 函数体中只能包含一条语句且是return语句
  • 函数体中不能定义局部变量,不能有循环语句、条件语句
  • 函数必须有返回值,返回值必须是常量表达式。函数如果有参数,必须传常量或常量表达式(不这样传参,要用普通变量接收函数结果,此时这个函数还是普通函数的运行执行)

constexpr函数的返回结果,必须用一个常量表达式进行接收,才能触发编译器的优化。否则这个函数还是像普通函数一样,在运行调用时才会计算:

constexpr int func1()
{
	return 1;
}

constexpr int func2(int x)
{
	return x + 10;
}

constexpr int func3(int n)
{
	return n <= 1 ? 1 : n * func3(n - 1);
}

int main()
{
	constexpr int a = func1();
	constexpr int b = func2(10);
	constexpr int c = func3(5);

	cout << a << endl;
	cout << b << endl;
	cout << c << endl;
	
	return 0;
}

通过汇编语句观察,确实在编译时期就计算出了a、b、c的结果,在cout语句中直接进行了值替换

constexpr还能修饰其他函数:

  • constexpr修饰构造函数,一个类的构造函数被constexpr修饰了,才能定义constexpr修饰的类对象。要求:
    • 该类的所有成员变量必须是字面类型
    • constexpr构造函数必须在初始化列表初始化所有成员变量,必须用常量表达式初始化成员变量,函数体必须为空
    • 该类的析构函数不能做任何实际清理工作
  • constexpr修饰成员函数:
    • 这个函数自动成为const成员函数,参数列表()后要加上const,函数内不能修改成员变量
    • constexpr成员函数不能是虚函数
    • 其他要求跟普通函数一样
  • constexpr修饰函数模板:
    • 如果constexpr修饰的函数模板的实例化结果不符合constexpr普通函数的要求,则constexpr会自动忽略,这个函数就相当于一个普通函数

能看出来,C++11对于constexpr修饰函数还是相当相当严格的,但这些规定会在后面的C++标准中逐步放开,时代还是在进步的~

二、constexpr在C++14中的进化

C++14中,大幅放宽了对constexpr函数的限制,使其语法和功能更接近普通函数:

  • 允许了constexpr函数内部声明和初始化局部变量
  • 支持函数内的if语句、for/while语句、switch语句等
  • 函数内可以有多条return语句
  • 函数的返回类型可以是void、自定义类型、STL的std::array容器、等更多复合类型

实验时,要确保编译器支持的是C++14标准:

constexpr int func(int n)
{
	if (n == 1 || n == 2)
	{
		return n;
	}

	int ret = 1; //允许局部变量
	for (int i = 2; i <= n; i++)
	{
		ret *= i;
	}

	return ret; //允许多条return语句
}

int main()
{
	constexpr int a = func(5);
	cout << a << endl;

	return 0;
}

三、constexpr在C++17中的进化

C++17进一步对constexpr扩展,使其能力大幅提升,进一步模糊了编译时和运行时的界限。

其中,constexpr可以修饰lambda表达式了,要求:

  • lambda表达式的捕获必须是常量表达式
  • 函数体满足constexpr函数的要求

同样,实验时保证编译器是C++17标准的:

int main()
{
	constexpr int a = 10;
	constexpr auto func = [a](int x) constexpr {return x * a; };
	constexpr int b = func(5);

	cout << b << endl;

	return 0;
}

四、constexpr在C++20中的进化

C++20是C++的最重要的更新之一,也同样对constexpr特性进行了革命,使编译器的计算能力得到前所未有的提升,使其成为现代C++元编程和性能优化的核心工具,主要更新了以下内容:

  • 支持了在编译期动态内存分配:C++20支持constexpr函数中使用动态内存分配(new/delete),但所分配的内存必须在编译器被释放。这一用法使得STL库的其他容器的编译期实现成为“可能”,但C++20还无法在constexpr函数内真正使用vector、string等容器涉及内存的操作,继续期待未来的更新吧。
  • 支持了constexpr函数内的try-catch语句,但是实际不能真正抛出异常。这里主要用于模板约束和编译期的错误检测,异常必须在编译期捕获和处理,不能传播到运行时期。
  • 支持了constexpr可变(mutable)类成员变量:原来的constexpr成员函数中,不能修改成员变量,但C++20后在定义成员变量时加上mutable关键字,这个成员变量就能在constexpr成员函数中被修改了。
  • 支持了constexpr虚函数:C++20后开始支持虚函数定义为constexpr函数,也就是支持了编译器的多态调用。

C++23是目前最新的C++标准,但编译器支持仍在路上,主要因为标准本身的复杂性、厂商对稳定性的优先考量以及实现策略的差异。让我们期待C++的未来无限发展可能吧!

五、C++20的consteval和constinit

constexpr的核心本质是允许在编译器进行计算。但如果上下文要求它在运行时计算,constexpr也可以像一个普通函数一样运行。

为了杜绝这种不确定性,C++20引入了consteval这个关键字,他比constexpr更严格。consteval修饰的函数必须在编译时被调用和求值,如果一个consteval函数不能在编译时被调用和求值,那么程序就会编译报错。

同时,C++20另一个关键字constinit,用于修饰具有静态存储期或线程存储期的变量(如全局变量,static变量),不能修饰局部变量。它的作用是强制该变量在编译期初始化完成。 适合用在需要确保编译时初始化但不要求变量保持常量的场景中。

到此这篇关于现代C++的constexpr特性小结的文章就介绍到这了,更多相关C++的constexpr特性内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • C++生成格式化的标准字符串实例代码

    C++生成格式化的标准字符串实例代码

    这篇文章主要给大家介绍了关于C++生成格式化的标准字符串的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用C++具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
    2019-09-09
  • 基于QT制作一个简易的传输文件小工具

    基于QT制作一个简易的传输文件小工具

    本文主要介绍了通过QT实现的一个文件传输小工具。功能就是能实现文件的双向传输,即客户端能传给服务端,服务端可以传给客户端。文中示例代码具有一定的学习价值,感兴趣的小伙伴可以了解一下
    2021-12-12
  • C++初始化列表学习

    C++初始化列表学习

    在C++中,struct和class的唯一区别是默认的克访问性不同,而这里我们不考虑访问性的问题,所以下面的代码都以struct来演示
    2013-09-09
  • C语言版飞机大战游戏

    C语言版飞机大战游戏

    这篇文章主要为大家详细介绍了C语言版飞机大战游戏,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-12-12
  • c++ vector造成的内存泄漏问题

    c++ vector造成的内存泄漏问题

    这篇文章主要介绍了c++ vector造成的内存泄漏问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-08-08
  • C++代码实现链队列详解

    C++代码实现链队列详解

    下面小编就为大家分享一篇C++代码实现链队列的示例,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧,希望能够给你带来帮助
    2021-09-09
  • 深入解析C++中的auto自动类型推导

    深入解析C++中的auto自动类型推导

    C++标准委员会在C++11标准中改变了auto关键字的语义,使它变成一个类型占位符,允许在定义变量时不必明确写出确切的类型,让编译器在编译期间根据初始值自动推导出它的类型,下面我们就来看看auto自动类型推导的推导规则吧
    2024-04-04
  • 一篇文章带你入门C语言数据结构:绪论

    一篇文章带你入门C语言数据结构:绪论

    这篇文章主要介绍了C语言的数据解构基础,希望对广大的程序爱好者有所帮助,同时祝大家有一个好成绩,需要的朋友可以参考下,希望能给你带来帮助
    2021-08-08
  • OpenCV图像分割中的分水岭算法原理与应用详解

    OpenCV图像分割中的分水岭算法原理与应用详解

    这篇文章主要为大家详细介绍了OpenCV图像分割中的分水岭算法原理与应用,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-01-01
  • C++内存序的操作方法

    C++内存序的操作方法

    在C++中,内存序(Memory Order)是一个非常重要的概念,特别是在多线程编程中,本文通过实例代码介绍C++内存序的相关知识,感兴趣的朋友一起看看吧
    2025-11-11

最新评论