C++11 新特性 常量表达式 constexpr

为了解决常量无法确定的问题,C++11在新标准中提出了关键字constexpr,它能够有效地定义常量表达式,并且达到类型安全、可移植、方便库和嵌入式系统开发的目的。

一、常量的不确定性

在C++11标准以前,我们没有一种方法能够有效地要求一个变量或者函数在编译阶段就计算出结果。由于无法确保在编译阶段得出结果,导致很多看起来合理的代码却引来编译错误。这些场景主要集中在需要编译阶段就确定的值语法中,比如case语句、数组长度、枚举成员的值以及非类型的模板参数。举个例子:

const int index0 = 0;
#define index1 1// case语句
switch (argc) {
case index0:std::cout << "index0" << std::endl;break;
case index1:std::cout << "index1" << std::endl;break;
default:std::cout << "none" << std::endl;
}const int x_size = 5 + 8;
#define y_size 6 + 7
// 数组长度
char buffer[x_size][y_size] = { 0 };// 枚举成员
enum {enum_index0 = index0,enum_index1 = index1,
};std::tuple<int, char> tp = std::make_tuple(4, '3');
// 非类型的模板参数
int x1 = std::get<index0>(tp);
char x2 = std::get<index1>(tp);

const定义的常量和宏都能在要求编译阶段确定值的语句中使用,上述代码都是有效的。但是这些代码并不可靠,C++程序员应该尽量少使用宏,因为预处理器对于宏只是简单的字符替换,完全没有类型检查。对const定义的常量可能是一个运行时常量,这种情况下是无法在case语句以及数组长度等语句中使用的。修改一下上述代码:

int get_index0() { return 0; }int get_index1() { return 1;}int get_x_size() { return 5 + 8; }int get_y_size() { return 6 + 7; }const int index0 = get_index0();
#define index1 get_index1()switch (argc)
{
case  index0:std::cout << "index0" << std::endl;break;
case index1:std::cout << "index1" << std::endl;break;
default:std::cout << "none" << std::endl;
}const int x_size = get_x_size();
#define y_size get_y_size()
char buffer[x_size][y_size] = { 0 };enum {enum_index0 = index0,enum_index1 = index1,
};std::tuple<int, char> tp = std::make_tuple(4, '3');
int x1 = std::get<index0>(tp);
char x2 = std::get<index1>(tp);

上述代码无法通过编译,因为宏定义的函数调用和const变量是在运行时确定的。

为了解决以上常量无法确定的问题,C++11在新标准中提出了关键字constexpr,它能够有效地定义常量表达式,并且达到类型安全、可移植、方便库和嵌入式系统开发的目的。

二、constexpr值

constexpr值即常量表达式值,是一个用constexpr说明符声明的变量或者数据成员,它要求该值必须在编译期计算。另外,常量表达式值必须被常量表达式初始化。

constexpr int x = 42;
char buffer[x] = { 0 };

从上述代码看,constexprconst是没有区别的,将关键字替换为const同样能达到目的。但是const并没有确保编译期常量的特性,所以在下面的代码中,它们会有不同的表现:

int x1 = 42;
const int x2 = x1;            // 定义和初始化成功
char buffer[x2] = { 0 };      // 编译失败,x2无法作为数组长度

在上面这段代码中,虽然x2初始化编译成功,但是编译器并不一定把它作为一个编译期需要确定的值,所以在声明buffer的时候会编译错误。这里是不一定,因为编译器的实现不一样,在GCC中,这段代码可以编译成功,但是MSVC和CLang则会编译失败。如果把const替换为constexpr,会有不同的情况发生:

int x1 = 42;
constexpr int x2 = x1;        // 编译失败,x2无法用x1初始化
char buffer[x2] = { 0 };

编译器编译第二句代码的时候就会报错,常量表达式值必须由常量表达式初始化,而x1并不是常量,明确地违反了constexpr的规则,编译器自然就会报错。可以看出,constexpr约束更强,它不仅要求常量表达式是常量,并且要求是一个编译阶段就能够确定其值的常量。

三、constexpr函数

常量表达式函数的返回值可以在编译阶段就计算出来。不过在定义常量表示函数时有更多的约束规则。

1、函数必须返回一个值,所以它的返回值类型不能是void

2、函数体必须只有一条语句:return expr,其中expr必须也是一个常量表达式。如果函数有形参,则将形参替换到expr中后,expr仍然必须是一个常量表达式。

3、函数使用之前必须有定义。

4、函数必须用constexpr声明。

constexpr int max_unsigned_char() { return 0xff; }constexpr int square(int x) { return x * x; }constexpr int abs(int x) { return x > 0 ? x : -x; }int main() {char buffer1[max_unsigned_char()] = { 0 };char buffer2[square(5)] = { 0 };char buffer3[abs(-8)] = { 0 };
}

上述代码定义了三个常量表达式函数,由于它们的返回值能够在编译期计算出来,因此可以直接将这些函数的返回值使用在数组长度的定义上。由于标准规定函数体中只能有一个表达式return expr,因此是无法使用if语句的,不过用条件表达式也能完成类似的效果。

让我们看一些错误的实例

// 返回void
constexpr void foo() { }// 不是一个常量表达式,试图修改x的值
constexpr int next(int x) { return ++x; }// g()不是一个常量表达式
int g() { return 42; }
constexpr int f() { return g(); }// 只有声明没有定义
constexpr int max_unsigned_char2(); 
enum {max_uchar = max_unsigned_char2()
}// 存在多条语句
constexpr int abs2(int x) {if (x > 0) {return x;} else {return -x;}
}// 存在多条语句
constexpr int sum(int x)
{int result = 0;while (x > 0){result += x--;}return result;
}

有了常量表达式函数的支持,C++标准对STL也做了一些改进,比如在<limits>中增加了constexpr声明,因此下面的代码也可以顺利编译成功了:

char buffer[std::numeric_limits<unsigned char>::max()] = { 0 };

四、constexpr构造函数

constexpr还能够声明用户自定义类型,例如:

struct X {int x1;
};constexpr X x = { 1 };
char buffer[x.x1] = { 0 };

上面的代码可以通过编译,constexpr声明和初始化了变量x。不过有时候我们不希望将变量暴露出来:

class X {
public:X() : x1(5) {}int get() const {return x1;}
private:int x1;
};constexpr X x;                    // 编译失败,X不是字面类型
char buffer[x.get()] = { 0 };     // 编译失败,x.get()无法在编译阶段计算

constexpr说明符不能用来声明自定义类型。解决这个问题只需要用constexpr声明X类的构造函数,当然这个构造函数也有一些规则需要遵循:

1、构造函数必须用constexpr声明。

2、构造函数初始化列表中必须是常量表达式。

3、构造函数的函数体必须为空(这一点基于构造函数没有返回值,所以不存在return expr)。

改写上述代码

class X {
public:constexpr X() : x1(5) {}constexpr X(int i) : x1(i) {}constexpr int get() const {return x1;}
private:int x1;
};constexpr X x;
char buffer[x.get()] = { 0 };

上述代码给构造函数和get函数添加了constexpr说明符就可以编译成功,因为它们本身都符合常量表达式构造函数和常量表达式函数的要求,我们称这样的类为字面量类类型(literal class type)。其实代码中constexpr int get()constconst有点多余,因为在C++11中,constexpr会自动给函数带上const属性。

常量表达式构造函数拥有和常量表达式函数相同的退化特性,当它的实参不是常量表达式的时候,构造函数可以退化为普通构造函数,当然,这么做的前提是类型的声明对象不能为常量表达式值:

int i = 8;
constexpr X x(i);     // 编译失败,不能使用constexpr声明
X y(i);               // 编译成功

由于i不是一个常量,因此X的常量表达式构造函数退化为普通构造函数,这时对象x不能用constexpr声明,否则编译失败。

使用constexpr声明自定义类型的变量,必须确保这个自定义类型的析构函数是平凡的,否则也是无法通过编译的。平凡析构函数必须满足下面3个条件。

1.自定义类型中不能有用户自定义的析构函数。

2.析构函数不能是虚函数。

3.基类和成员的析构函数必须都是平凡的。

五、对浮点的支持

constexpr支持声明浮点类型的常量表达式值,而且标准还规定其精度必须至少和运行时的精度相同:

constexpr double sum(double x) { return x > 0 ? x + sum(x - 1) : 0; }constexpr double x = sum(5);

六、C++14对常量表达式的增强

C++14标准对常量表达式函数的改进如下:

1、函数体允许声明变量,除了没有初始化、staticthread_local变量
2、函数允许出现ifswitch语句,不能使用go语句
3、函数允许所有的循环语句,包括forwhiledo-while
4、函数可以修改生命周期和常量表达式相同的对象
5、函数的返回值可以声明为void
6、constexpr声明的成员函数不再具有const属性

在C++11中无法成功编译的常量表达式函数,在C++14中可以编译成功了:

// 基于规则2
constexpr int abs2(int x) {if (x > 0) {return x;} else {return -x;}
}// 基于规则1和规则3
constexpr int sum(int x) {int result = 0;while (x > 0) {result += x--;}return result;
}// 基于规则4
constexpr int next(int x) {return ++x;
}

同样这些改进也会影响常量表达式构造函数

class X {
public:constexpr X() : x1(5) {}constexpr X(int i) : x1(0) {if (i > 0) {x1 = 5;}else {x1 = 8;}}constexpr void set(int i) { x1 = i; }constexpr int get() const { return x1; }
private:int x1;
};constexpr X make_x() {X x;x.set(42);return x;
}int main() {constexpr X x1(-1);constexpr X x2 = make_x();constexpr int a1 = x1.get();constexpr int a2 = x2.get();std::cout << a1 << std::endl;std::cout << a2 << std::endl;
}

上述代码的运行结果是:

image-2155105029

main函数里的4个变量x1x2a1a2都有constexpr声明,也就是说它们都是编译期必须确定的值。首先对于常量表达式构造函数,我们发现可以在其函数体内使用if语句并且对x1进行赋值操作了。可以看到返回类型为voidset函数也被声明为constexpr了,这也意味着该函数能够运用在constexpr声明的函数体内,make_x函数就是利用了这个特性。根据规则4和规则6,set函数也能成功地修改x1的值了。

七、constexpr lambda表达式

从C++17开始,lambda表达式在条件允许的情况下都会隐式声明为constexpr。这里所说的条件,即生成constexpr函数的规则。看一个例子:

constexpr int foo() { return []() { return 58; }(); }auto get_size = [](int i) { return i * 2; };
char buffer1[foo()] = { 0 };
char buffer2[get_size(5)] = { 0 };

lambda表达式不满足constexpr的条件时,lambda表达式也不会出现编译错误,它会作为运行时lambda表达式存在:

// 情况1
int i = 5;
auto get_size = [](int i) { return i * 2; };
char buffer1[get_size(i)] = { 0 };
int a1 = get_size(i);// 情况2
auto get_count = []() {static int x = 5;return x;
};
int a2 = get_count();

对于情况1,上述代码按理说会编译失败,但是在GCC中由于支持了变长数组,所以是可以通过编译的,但如果你尝试在严格遵循C++标准的编译器上编译这段代码例如MSVC和CLang,则会出错。
对于情况2,由于static变量的存在,lambda表达式对象get_count不可能在编译期运算,因此它最终会在运行时计算。

值得注意的是,我们也可以强制要求lambda表达式是一个常量表达式,用constexpr去声明它即可:

auto get_size = [](int i) constexpr -> int { return i * 2; };

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/838240.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

UBUNTU下CMAKE指定执行文件运行时查找库的路径

在Ubuntu下&#xff0c;使用CMake时&#xff0c;如果需要指定执行文件运行时库的搜索路径&#xff0c;可以在CMakeLists.txt文件中通过set_target_properties命令来设置。 以下是一个示例&#xff0c;假设你的目标是一个名为my_application的可执行文件&#xff0c;你想要添加…

LLama3大模型本地部署 仅需6步完成对话模型本地安装部署。附送可视化ui安装、自定义模型目录,修改模型保存地址,第三方微调模型、中文模型下载地址

本篇分为三部分 一&#xff1a;6步完成llama3大模型本地部署 二&#xff1a;8步完成llama3可视化对话界面安装 三&#xff1a;重设模型文件路径 四&#xff1a;微调模型、中文模型下载资源分享 一、LLama3 大模型本地部署安装 首先去mata官网下载ollama客户端 Ollama 选择合适…

如何使用正则表达式避免生成空字符串

在字符串处理中&#xff0c;我们经常需要将字符串根据特定的分隔符拆分成多个子字符串。Java 中的 split 方法可以很好地完成这项任务。但是&#xff0c;在使用 split 方法时&#xff0c;如果分隔符连续出现&#xff0c;可能会生成空字符串。 可以使用正则表达式避免生成空字符…

linux 环境下 分布式文件搭建fastDFS

1.软件信息 地址&#xff1a;happyfish100 (YuQing) GitHub 1.fastdfs-master.zip 2.fastdfs-nginx-module-master.zip 3.libfastcommon-master.zip 4.libserverframe-master.zip yum install make cmake gcc gcc-c perl 2.安装libfastcommon unzip libfastcommon-mast…

MQTT_客户端安装_1.4

下载地址 MQTTX 下载 下一步直接安装即可 界面介绍

人工智能项目,如何解决大模型的数据私有化

这个问题是最近走访百家企业&#xff0c;客户问的最多的问题。人工智能是对数据集中后&#xff0c;再利用的智能化手段&#xff0c;ChatGPT还在持续的投入&#xff0c;汇集数据、训练模型&#xff0c;微软也不过是做了一个办公客户端的智能工具&#xff0c;那么行业应运之时&am…

基于CentOS-7搭建hadoop3.3.6大数据集群(保姆级教程)

目录 安装虚拟机 为hadoop用户添加权限 关闭防火墙 修改主机名以及ip地址映射 配置ip 连接xshell &#xff0c;以hadoop用户登录 创建目录并将该文件夹权限赋予hadoop用户 安装配置jdk 关闭虚拟机&#xff0c;克隆其他两个节点 修改主机名和ip地址 配置免密登录 安装…

如何通过专业的二手机店erp优化手机商家运营!

在数字化浪潮席卷全球的大背景下&#xff0c;手机行业作为科技发展的前沿阵地&#xff0c;正经历着前所未有的变革。对于众多手机商家而言&#xff0c;如何在这场变革中抢占先机&#xff0c;实现数字化转型&#xff0c;成为了摆在他们面前的一大难题。幸运的是&#xff0c;途渡…

工作绩效域怎么写和子题目如何回应

某智慧案管信息化系统 在2023年11月&#xff0c;我作为项目经理&#xff0c;领导了“某市XX智慧案管信息化系统”项目的建设工作。该项目以485.5万元的合同金额成功中标&#xff0c;预计建设周期为12个月。作为一个多维度的系统工程&#xff0c;它由案件受理、分配、跟踪、审理…

python如何在图片上写斜体字

在Python中&#xff0c;直接在图片上写斜体文字通常不是图像库&#xff08;如PIL或OpenCV&#xff09;的内置功能&#xff0c;因为这些库主要关注于图像处理而非复杂的文本渲染。然而&#xff0c;你可以通过几种方式在图片上创建斜体效果&#xff1a; 使用PIL&#xff08;Pytho…

API低代码平台介绍3-异构数据源的数据查询功能

异构数据源的数据查询功能 在上一篇文章中我们通过API平台定义了一个最基本的数据查询接口&#xff0c;本篇文章我们将上升难度&#xff0c;在原有接口的基础上&#xff0c;实现在MySQL数据库和Oracle数据库同时进行数据查询。   什么场景会需要同时对异构数据源进行查询&…

基于FPGA的NC图像质量评估verilog实现,包含testbench和MATLAB辅助验证程序

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 vivado2019.2和matlab2022a测试&#xff0c;结果如下&#xff1a; 2.算法运行软件版本 vivado2019.2 matlab2022a 3.部分核心程序 timescale …

【C语言习题】6.逆序输出

文章目录 1.描述输入描述&#xff1a;输出描述&#xff1a;示例图&#xff1a; 2.解题思路3.具体代码4.代码讲解 1.描述 输入10个整数&#xff0c;要求按输入时的逆序把这10个数打印出来。逆序输出&#xff0c;就是按照输入相反的顺序打印这10个数。 输入描述&#xff1a; 一…

苹果与谷歌联合推出跨平台反跟踪器

近日&#xff0c;苹果公司发布了iOS 17.5&#xff0c;新版本新增了跨平台的反追踪检测功能。这一功能是与谷歌合作开发的&#xff0c;意味着苹果的“查找我的”和谷歌的新“查找我的设备”网络将携手合作&#xff0c;共同抵制不必要的追踪和骚扰行为。如果检测到非你所有的追踪…

SDL系列(三)—— SDL2.0 扩展库:SDL_image与SDL_mixer

SDL_image SDL 默认支持的&#xff0c;只能打开 BMP 格式的图片 。 然而我们常见的是 Png jpg 格式的图片&#xff0c;于是我们这节完成 SDL 借用 自带的三方库 &#xff0c;来 完成加载渲染 png 等其他图片格式。 SDL_image 简介 使用 SDL_image &#xff0c;您…

自动驾驶技术:现状、挑战与前景

自动驾驶技术是当今汽车行业的热门话题&#xff0c;它的发展前景备受关注&#xff0c;同时也面临着诸多挑战和限制。在这篇文章中&#xff0c;我将探讨自动驾驶技术的现状、挑战&#xff0c;并展望其未来的发展前景。 现状&#xff1a; 自动驾驶技术已经取得了一定的进…

MBGP MPLS VPN

一、MPLS的不足与发展 1、在90年代初期&#xff0c;各个厂商的硬件设备性能不足时&#xff0c;MPLS能够很好的替代掉传统路由基于IP路由表多次查表转发数据的问题&#xff0c;但随着硬件性能的不断攀升&#xff0c;MPLS在转发性能上的优势几乎丧失掉&#xff1b; 2、但MPLS由…

[笔试训练](二十三)067:打怪068:字符串分类069:城市群数量

目录 067:打怪 068:字符串分类 069:城市群数量 067:打怪 题目链接:打怪 (nowcoder.com) 题目&#xff1a; 题解&#xff1a; 直接计算结果&#xff1a; 1.一只怪物能抗几次攻击 int m(H/a)(H%a0?0:1); 2.杀死一只怪物&#xff0c;玩家要抗几次攻击 int nm-1; *3.杀死一只…

Lc33---- 414. 第三大的数(java版)---排序(去重/复制到新数组)

1.题目描述 给你一个非空数组&#xff0c;返回此数组中 第三大的数 。如果不存在&#xff0c;则返回数组中最大的数。 示例 1&#xff1a; 输入&#xff1a;[3, 2, 1] 输出&#xff1a;1 解释&#xff1a;第三大的数是 1 。 示例 2&#xff1a; 输入&#xff1a;[1, 2] 输出…

jmeter指南:JMeter 安装、配置和性能测试

使用 JMeter 进行性能测试 1. Java 版本要求 JMeter 要求与 Java 8 或更高版本兼容。为了确保安全性和性能&#xff0c;建议安装最新次要版本的主要 Java 版本。鉴于 JMeter 仅使用标准 Java API&#xff0c;如果由于 JRE 实现问题而无法运行 JMeter&#xff0c;请不要提交错…