新特性之C++17

目录

u8字符字面量
noexcept作为类型系统的一部分
Lambda表达式捕获*this
constexpr新特性
编译期的if constexpr语句
constexpr的Lambda表达式
变量新特性
inline变量
结构化绑定
if和switch语句中的初始化器
强制的复制省略(返回值优化)
临时物质化
模板新特性
折叠表达式(…)
类模板实参推导
auto占位的非类型模板形参

u8字符字面量

C++17 引入了u8字符字面量,用于表示 UTF-8 编码的字符串。

auto str = u8"Hello, 世界";

noexcept作为类型系统的一部分

noexcept是 C++11 引入的一个关键字,用于改善C++中异常处理的性能和可用性。noexcept指定的函数保证不会抛出异常,这使得编译器有机会进行优化,同时也为程序员提供了一个清晰的工具来指明哪些函数是安全的,即不会因为异常而失败。
C++17对其改动主要如下:

  1. 更广泛的使用

C++17 标准库在很多已有的函数中增加了noexcept说明。这是因为对异常安全性有更高的要求和对性能优化的关注。比如,在移动语义和智能指针等方面,更频繁地看到noexcept的使用。

  1. 推导规则

C++17 引入了新的推导指南,使得noexcept能够在模板和自动类型推导中得到更好的处理。这包括在模板函数和自动返回类型中,noexcept的状态可以被推导出来。例如,一个模板函数可以根据其模板参数的操作是否不抛出异常,来决定自身是否声明为noexcept

  1. 移动操作的默认noexcept

在 C++11 和 C++14 中,移动构造函数和移动赋值操作不会自动被推断为noexcept,而在 C++17 中,如果一个类的所有成员和基类的移动构造函数和移动赋值操作都是noexcept的,那么这个类的移动操作也会被推断为noexcept。这改善了容器(如 std::vector)在元素类型是可移动但不抛出异常时的性能,因为容器可以安全地进行更优化的内存操作,如使用realloc而不是手动复制。

  1. 对std::swap的优化

C++17 中,std::swap在可能的情况下使用noexcept来确保不抛出异常,这对于某些类型来说,特别是在模板编程中,可以提高效率和安全性。

Lambda表达式捕获*this

C++17的Lambda引入捕获*this,使得可以捕获当前对象的常量副本,相当于是以值捕获的形式捕获了this指向的对象,并且赋予const属性。

class A
{int a = 1;
public:void printCopyA(){auto lambda = [*this] {a++; // error C3490: 由于正在通过常量对象访问“a”,因此无法对其进行修改std::cout << a;};lambda();}void printA(){auto lambda = [&] {a++;std::cout << a;};lambda();}
};int main()
{A a;a.printA();a.printCopyA();
}

constexpr新特性

编译期的if constexpr语句

C++17引入if constexpr语句,这是一个在编译时决定条件分支的语言特性。主要是为了增强模板和编译时多态的功能,使得基于模板参数的条件编译变得更为直接和清晰。

#include <iostream>
#include <type_traits>template<typename T>
void process(T value) {if constexpr (std::is_integral_v<T>) {std::cout << "Integral type with value: " << value << std::endl;} else if constexpr (std::is_floating_point_v<T>) {std::cout << "Floating-point type with value: " << value << std::endl;} else {std::cout << "Other type" << std::endl;}
}int main() {process(10);    // 输出:Integral type with value: 10process(3.14);  // 输出:Floating-point type with value: 3.14process("Hello");  // 输出:Other type
}

特点和限制

  • 编译时求值if constexpr的条件必须能在编译时得到求值。
  • 作用域限制:只有符合条件的分支会被编译,这意味着在不符合条件的分支中的代码即使含有编译错误,也不会引起编译失败,因为这部分代码根本不会被编译。

例如下面代码,在else分支下调用不存在的函数,编译不会失败:

#include <iostream>
#include <type_traits>template<typename T>
void process(T value) {if constexpr (std::is_integral_v<T>) {std::cout << "Integral type with value: " << value << std::endl;} else if constexpr (std::is_floating_point_v<T>) {std::cout << "Floating-point type with value: " << value << std::endl;} else {nonExistentFunc(value); // 不会被编译std::cout << "Other type" << std::endl;}
}int main() {process(10);    // 输出:Integral type with value: 10process(3.14);  // 输出:Floating-point type with value: 3.14
}

constexpr的Lambda表达式

C++17 允许Lambda表达式在constexpr上下文中使用,从而使其可以在编译期求值。

constexpr auto add = [](int a, int b) { return a + b; };
static_assert(add(2, 3) == 5);

变量新特性

inline变量

C++17 引入的内联变量是一个重要的语言特性,它主要解决了多个编译单元之间共享全局变量的链接问题,特别是对于模板和头文件中定义的变量。在此之前,全局变量或静态成员变量的定义可能会导致多个定义问题(One Definition Rule,ODR)的违规,尤其是在涉及到头文件中包含的变量时。

内联变量的用途

  1. 解决多个定义问题(ODR):在 C++ 中,非内联变量在多个源文件中定义时,会违反 ODR,导致链接错误。内联变量允许在多个编译单元中定义同一个变量,编译器保证在程序中只有一个实例。
  2. 简化模板和头文件中的变量定义:C++17 之前,如果在头文件中定义静态成员变量或全局变量,通常需要在一个源文件中提供该变量的定义以避免链接时的重复定义问题。内联变量允许在头文件中直接定义并初始化变量,简化了代码结构。

语法

内联变量的声明非常直接,只需要在变量声明前加上 inline 关键字:

inline int myGlobalVar = 10;

结构化绑定

C++17 引入的结构化绑定(Structured Bindings)是一种新的语言特性,旨在提供一种简洁、直观的方式来解包(unpack)元组、结构体或数组中的数据到单独的变量中。这个特性极大地增强了代码的可读性和易用性,特别是在处理复合数据类型或从函数返回多个值时。

工作原理

结构化绑定允许你同时定义多个变量,将它们绑定到一个聚合数据类型(如元组、数组、结构体或配对)的各个成员上。这些变量可以被视作原始数据结构中对应成员的别名。

基本语法

结构化绑定的基本语法如下:

auto [x, y, z] = expression;

其中 expression 必须是一个返回聚合类型(如元组、结构体、数组)的表达式。x, y, z 等变量被创建为引用或值,这取决于表达式的类型和上下文。

使用场景

  • 从函数返回多个值: 使用结构化绑定,函数可以返回一个 std::tuple 或 std::pair,调用者可以非常直观地获取这些值。
  • 解包数组和元组: 直接将数组或元组的元素绑定到变量上,简化数组或元组的处理代码。
  • 访问结构体成员: 对于简单的 POD(Plain Old Data)类型的结构体,可以直接绑定到其成员上,而不需要逐一指定。

示例

  1. 元组解包
#include <tuple>
#include <iostream>std::tuple<int, double, std::string> getTuple() {return {42, 3.14, "Hello"};
}int main() {auto [a, b, c] = getTuple();std::cout << a << ", " << b << ", " << c << std::endl;  // 输出:42, 3.14, Hello
}
  1. 结构体解包
#include <iostream>struct Point {int x, y;
};int main() {Point p{10, 20};auto [x, y] = p;std::cout << x << ", " << y << std::endl;  // 输出:10, 20
}
  1. 数组解包
#include <iostream>int main() {int arr[] = {1, 2, 3};auto [a, b, c] = arr;std::cout << a << ", " << b << ", " << c << std::endl;  // 输出:1, 2, 3
}

if和switch语句中的初始化器

C++17引入ifswitch语句的初始化器,使得在使用条件判断时可以在内部进行变量初始化。

if示例

#include <iostream>
#include <map>int main() {std::map<std::string, int> myMap = {{"Alice", 5}, {"Bob", 10}};if (auto it = myMap.find("Alice"); it != myMap.end()) {std::cout << "Found Alice with score " << it->second << std::endl;} else {std::cout << "Alice not found" << std::endl;}
}

switch示例

#include <iostream>
#include <vector>int main() {std::vector<int> numbers = {10, 20, 30, 40};switch (auto i = numbers.size(); i) {case 4:std::cout << "There are four elements." << std::endl;break;case 3:std::cout << "There are three elements." << std::endl;break;default:std::cout << "The number of elements is not 3 or 4." << std::endl;}
}

优点

  • 作用域控制:初始化的变量仅在 if 或 switch 语句块中有效,限制了变量的作用域,避免了不必要的作用域泄露。
  • 代码清晰和紧凑:通过将相关的初始化和条件判断放在一起,代码更加整洁,逻辑更清晰。
  • 避免前置声明:不需要在 if 或 switch 前面单独声明变量,减少了代码行数和复杂性。

强制的复制省略(返回值优化)

C++17 引入的强制的复制省略(Guaranteed Copy Elision)或者更准确地称作返回值优化(Return Value Optimization,RVO)的强化版本,是一个重要的编译器优化特性,它可以显著减少或消除某些情况下的对象复制和移动操作。这种优化不仅提高了程序的性能,还改善了资源管理,特别是在涉及到大型对象或者资源密集型对象时。

背景与问题

在 C++17 之前,返回局部对象时通常会涉及到复制或移动构造函数的调用,即使编译器应用了返回值优化(RVO)或命名返回值优化(NRVO)。但这些优化是可选的,不是强制的,这意味着编译器可以选择不进行这些优化,从而导致性能损失。

C++17 的改变

C++17 通过修改语言的核心规则(有时被称为“强制 RVO”或“保证复制省略”),确保在某些特定情况下消除这些复制和移动操作。这主要体现在两个方面:

  1. 当对象从函数返回时:C++17 要求编译器必须省略局部对象的复制和移动操作,直接在调用方的上下文中构造这些对象。
  2. 从表达式构造新对象时:例如在使用列表初始化或直接初始化对象时。

技术细节

具体来说,C++17 标准规定,如果返回的对象类型与函数返回类型相符,并且返回的是一个局部对象或临时对象,编译器必须省略复制或移动构造函数的调用,直接在接收对象的内存位置构造返回对象。

示例

以下示例展示了 C++17 中的强制复制省略如何工作:

#include <iostream>
#include <vector>class BigObject {
public:std::vector<int> data;BigObject() {std::cout << "Constructor called" << std::endl;}BigObject(const BigObject&) {std::cout << "Copy constructor called" << std::endl;}BigObject(BigObject&&) noexcept {std::cout << "Move constructor called" << std::endl;}
};BigObject createBigObject() {BigObject obj;obj.data.resize(1000);  // 假设是一个资源密集型操作return obj;
}int main() {BigObject myObj = createBigObject();// 应该看不到复制或移动构造函数的调用信息return 0;
}

在 C++17 中运行这段代码时,你不会看到复制构造函数或移动构造函数被调用的信息,因为编译器直接在 myObj 的存储位置构造了 obj。

优点

  • 性能提升:避免不必要的复制和移动操作,特别是对于大对象或资源密集型对象。
  • 简化语义:程序员不需要依赖于编译器是否会应用(N)RVO来保证性能,因为现在这些优化是由语言规范保证的。

临时物质化(Temporary Materialication)

在 C++17 中引入了一个重要的概念:临时物质化(Temporary Materialization)。这个特性涉及到临时对象的创建,尤其是在需要对象实体时,如在传递参数、初始化、返回值等场景中。

背景

在早期的 C++ 标准中,临时对象(比如由表达式产生的未命名对象)的行为有时候可能会造成理解和使用上的混淆,特别是在它们与引用绑定、返回值和函数参数传递等方面。C++17 对这些规则进行了明确,确保临时对象的行为更加直观和可预测。

临时物质化的定义

临时物质化是指在需要一个完整的对象时,将一个临时的prvalue(纯右值)表达式转换为一个临时对象的过程。这主要发生在以下几种情况:

  1. 当 prvalue 需要作为引用的初始化值时: 如果一个 prvalue 被用作初始化一个引用,那么这个 prvalue 将会物质化为一个临时对象,以便引用可以绑定到它上面。
  2. 在 prvalue 作为函数参数传递时: 如果函数参数是按值传递的,而传递的实参是 prvalue,那么这个 prvalue 将物质化为一个临时对象,然后将其传递给函数。
  3. 在 prvalue 作为函数的返回值时: 当函数返回一个 prvalue 时,这个 prvalue 通常会物质化为一个临时对象,特别是在涉及到返回类型转换时。

示例

下面是一些示例,展示了 C++17 中临时物质化的具体应用:

#include <iostream>struct A {int value;A(int v) : value(v) { std::cout << "A(" << value << ") constructed\n"; }A(const A& other) : value(other.value) { std::cout << "A copied\n"; }
};A getA() {return A(5); // 返回 prvalue
}void takeA(A a) {std::cout << "Received A: " << a.value << std::endl;
}int main() {const A& aRef = A(10); // prvalue 物质化为临时对象,引用绑定到它takeA(A(20)); // prvalue 物质化为临时对象,传递给函数A myA = getA(); // prvalue 物质化过程return 0;
}

在这个例子中,每次 A(数字) 被调用时,都创建了一个 prvalue,随后在需要时物质化为一个临时对象。这些临时对象被用来初始化引用、作为函数参数,或直接赋值给变量。

模板新特性

折叠表达式(…)

在了解折叠表达式前先了解C++11引入的形参包,模板形参包 是一个与可变参数模板(Variadic Templates)紧密相关的概念。形参包允许你在函数或模板定义中接受不确定数量的模板参数或函数参数,使得模板和函数可以具有通用性和灵活性,能够处理任意数量和类型的输入参数。

语法

( 形参包 运算符... ) // 一元右折叠
( ...运算符 形参包 ) // 一元左折叠
( 形参包 运算符...运算符 初值 ) // 二元右折叠
( 初值 运算符...运算符 形参包 ) // 二元左折叠

其实一元和二元的概念是一样的,只是二元折叠要多一个初值参数。

示例说明

  1. 一元右折叠
#include <iostream>
#include <string>template<typename... Args>
std::string concatenateRight(Args... args) {return (args + ... + std::string(""));  // 一元右折叠
}int main() {std::cout << "Concatenate Right: " << concatenateRight("Hello", " ", "World", "!") << std::endl; // 打印:Concatenate Right: Hello World!
}
  1. 一元左折叠
#include <iostream>
#include <string>template<typename... Args>
std::string concatenateLeft(Args... args) {return (std::string("") + ... + args);  // 一元左折叠
}int main() {std::cout << "Concatenate Left: " << concatenateLeft("Hello", " ", "World", "!") << std::endl; // 打印:Concatenate Left: Hello World!
}
  1. 二元右折叠
#include <iostream>template<typename... Args>
int sum(int init, Args... args) {return (args + ... + init); // 二元右折叠
}int main() {std::cout << "Sum with initial 10: " << sum(10, 1, 2, 3) << '\n'; // 打印:16
}
  1. 二元左折叠
#include <iostream>template<typename... Args>
int sum(int init, Args... args) {return (init + ... + args); // 二元左折叠
}int main() {std::cout << "Sum with initial 10: " << sum(10, 1, 2, 3) << '\n'; // 打印:16
}

类模板实参推导

在C++17之前,当你使用模板类时,通常需要显式地指定所有的模板参数。例如,使用标准库中的std::pairstd::vector时,你需要这样写:

std::vector<int> v = {1, 2, 3};
std::pair<int, double> p = {1, 3.14};

但是有了类模板实参推导,你可以省略模板参数:

std::vector v = {1, 2, 3};  // 推导为 std::vector<int>
std::pair p = {1, 3.14};    // 推导为 std::pair<int, double>

auto占位的非类型模板形参

什么是非类型模板参数

非类型模板参数(Non-type template parameters)是C++模板编程中的一种强大的特性,允许在定义模板时使用一个具体的值而不是类型作为模板参数。这种参数可以是一个整数、枚举、指针或引用,甚至某些类的常量表达式,它们用于生成依赖于这些值的模板实例。

示例说明

template<auto Value>
class Constant
{
};int main()
{std::vector<int> vv{1, 2, 3};// 使用例子Constant<5>    intConst;    // 推导为 Constant<int>Constant<'a'>  charConst;   // 推导为 Constant<char>//Constant<3.14> doubleConst; // 推导为 Constant<double>
}

上面示例代码中使用std::vector时传递的形参必须是一个类型,而不能是一个值;但使用Constant模板可以传递具体的值,因为它通过auto占位符来使编译期间自动推导出具体的类型。

上面示例中在使用double类型的值进行实例化时会编译失败,是因为在C++20前非类型模板形参不能是double类型。

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

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

相关文章

【数据结构与算法】堆排序算法原理与实现:基于堆实现的高效排序算法

&#x1f493; 博客主页&#xff1a;倔强的石头的CSDN主页 &#x1f4dd;Gitee主页&#xff1a;倔强的石头的gitee主页 ⏩ 文章专栏&#xff1a;《数据结构与算法》 期待您的关注 ​ 目录 一、引言 堆排序的简介 堆排序的特点 二、堆的概念 三、堆排序算法的原理 四、堆…

软件测试面试1000问(含答案)

1、自动化代码中,用到了哪些设计模式? 单例设计模式工厂模式PO设计模式数据驱动模式面向接口编程设计模式 2、什么是断言( Assert) ? 断言Assert用于在代码中验证实际结果是不是符合预期结果&#xff0c;如果测试用例执行失败会抛出异常并提供断言日志 3、什么是web自动化…

数据结构预科

在堆区申请两个长度为32的空间&#xff0c;实现两个字符串的比较【非库函数实现】 要求&#xff1a; 1> 定义函数&#xff0c;在对区申请空间&#xff0c;两个申请&#xff0c;主函数需要调用2次 2> 定义函数&#xff0c;实现字符串的输入&#xff0c;void input(char …

Jenkins容器的部署

本文主要是记录如何在Centos7上安装docker,以及在docker里面配置tomcat、mysql、jenkins等环境。 一、安装docker 1.1 准备工作 centos7、VMware17Pro 1.2 通过yum在线安装dokcer yum -y install docker1.3 启动docker服务 systemctl start docker.service1.4 查看docke…

Java传引用问题

本文将介绍 Java 中的引用传递&#xff0c;包括其定义、实现方式、通过引用修改原来指向的内容和通过引用修改当前引用的指向的区别 目录 1、引用传递的概念 2、引用传递的实现方式 3、传引用会发生的两种情况&#xff1a; 通过引用修改当前引用的指向 通过引用修改原来指…

《数据仓库与数据挖掘》 总复习

试卷组成 第一章图 第二章图 第三章图 第四章图 第五章图 第六章图 第九章图 第一章 DW与DM概述 &#xff08;特点、特性&#xff09; DB到DW 主要特征 &#xff08;1&#xff09;数据太多&#xff0c;信息贫乏&#xff08;Data Rich&#xff0c; Information Poor)。 &a…

H2 Database Console未授权访问漏洞封堵

背景 H2 Database Console未授权访问&#xff0c;默认情况下自动创建不存在的数据库&#xff0c;从而导致未授权访问。各种未授权访问的教程&#xff0c;但是它怎么封堵呢&#xff1f; -ifExists 很简单&#xff0c;启动参数添加 -ifExists &#xff0c;它的含义&#xff1a…

【机器学习】机器学习的重要方法——线性回归算法深度探索与未来展望

欢迎来到 破晓的历程博客 引言 在数据科学日益重要的今天&#xff0c;线性回归算法以其简单、直观和强大的预测能力&#xff0c;成为了众多领域中的基础工具。本文将详细介绍线性回归的基本概念、核心算法&#xff0c;并通过五个具体的使用示例来展示其应用&#xff0c;同时探…

CASS7.0按方向和距离绘制图形

1、绘制工具 2、按方向和距离绘制 &#xff08;1&#xff09;切换方向 &#xff08;2&#xff09;距离输入

Python函数缺省参数的 “ 坑 ” (与C++对比学习)

我们都知道Python函数的缺省参数可以降低我们调用函数的成本&#xff0c;但是一般我们的缺省参数都是不可变对象&#xff0c;如果是可变对象&#xff0c;我们对其多次调用会发生什么呢&#xff1f; def func(arr[]):arr.append(Hello)print(arr)func() func() func() 这貌似…

MongoDB-社区版-本地安装

系统&#xff1a;win10 1. 下载server:Download MongoDB Community Server | MongoDB 我选的zip包 2. 下载shell&#xff1a;MongoDB Shell Download | MongoDB 我选的zip包 3. 启动server 4. 启动shell, 完成

MYSQL函数进阶详解:案例解析(第19天)

系列文章目录 一、MySQL的函数&#xff08;重点&#xff09; 二、MySQL的窗口函数&#xff08;重点&#xff09; 三、MySQL的视图&#xff08;熟悉&#xff09; 四、MySQL的事务&#xff08;熟悉&#xff09; 文章目录 系列文章目录前言一、MySQL的函数1. 聚合函数2. group_c…

Redis 多数据源自定义配置 Spring Boot 升级版

文章目录 1.前言2.git 示例地址3.需求4.代码实现4.1 application.properties 配置文件4.2 获取 application.properties 中的 redis 配置4.2.1 Environment 对象来获取自定义 redis 配置 4.3 初始化 RedisTemplate 对象&#xff0c;并注册到 Spring IOC 容器4.3.1 初始化方法4.…

spring boot (shiro)+ websocket测试连接不上的简单检测处理

1、用前端连接测试的demo一切正常&#xff0c;但是到了项目中连接不上了 一开始以为是地址错&#xff0c;但是换了apifox测试也是不可以。 2、考虑是shiro进行了拦截了&#xff0c;所以就访问不到了地址&#xff0c;那么就放行。 3、再次用apifox测试&#xff0c;成功了。 当然…

马拉松报名小程序的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;用户管理&#xff0c;赛事信息管理&#xff0c;赛事报名管理&#xff0c;活动商城管理&#xff0c;留言板管理&#xff0c;系统管理 微信端账号功能包括&#xff1a;系统首页&#xff0c;赛事信息&…

C++:智能指针

目录 前言 1.内存泄漏及其危害 2.内存泄漏分类&#xff1a; 3.如何检测内存泄漏 4.如何避免内存泄漏 一、为什么需要智能指针&#xff1f; 二、智能指针的使用及其原理 1.RAII 2.智能指针 3.std::auto_ptr 4.std::unique_ptr 5.std::shared_ptr 6.std::weak_ptr…

SA 注册流程

目录 1. UE开机后按照3GPP TS 38.104定义的Synchronization Raster搜索特定频点 2.UE尝试检测PSS/SSS&#xff0c;取得下行时钟同步&#xff0c;并获取小区的PCI&#xff1b;如果失败则转步骤1搜索下一个频点&#xff1b;否则继续后续步骤&#xff1b; 3.解析Mib&#xff0c;…

从0到1构建渠道运营体系:实战案例与策略指南

引言 在当今竞争激烈的市场环境中&#xff0c;有效的渠道运营是企业实现产品或服务快速触达目标用户、提升市场份额的关键。从零开始构建一个高效的渠道运营体系&#xff0c;不仅需要深思熟虑的策略规划&#xff0c;还需要灵活应变的实战操作。本文将结合实战案例&#xff0c;…

JDK新特性之协程

在 JVM 中&#xff0c;java 线程直接映射内核线程&#xff0c;因此 java 线程的创建、销毁和调度都要依赖内核态的操作&#xff08;系统调用&#xff09;。而协程是真正的用户线程&#xff0c;如上图所示很多的协程可以映射很少的几个内核线程&#xff0c;并且协程的创建、销毁…

gitee代码初次上传步骤

ps. 前提是已经下载安装gitee 一、在本地项目目录下空白处右击&#xff0c;选择“Git Bash Here” 二、初始化 git init 三、添加、提交代码&#xff08;注意add与点之间的空格&#xff09; git add . git commit -m 添加注释 四、连接、推送到gitee仓库 git remote add …