新特性之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.mzph.cn/news/866351.shtml

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

相关文章

第二届计算机、视觉与智能技术国际会议(ICCVIT 2024)

随着科技的飞速发展&#xff0c;计算机、视觉与智能技术已成为推动现代社会进步的重要力量。为了汇聚全球顶尖专家学者&#xff0c;共同探讨这一领域的最新研究成果和前沿技术&#xff0c;第二届计算机、视觉与智能技术国际会议&#xff08;ICCVIT 2024&#xff09;将于2024年1…

【Unity navmeshaggent 组件】

【Unity navmeshaggent 组件】 组件概述&#xff1a; NavMeshAgent是Unity AI系统中的一个组件&#xff0c;它允许游戏对象&#xff08;通常是一个角色或AI&#xff09;在导航网格&#xff08;NavMesh&#xff09;上自动寻路。 组件属性&#xff1a; Radius&#xff1a;导航…

华为机试HJ11数字颠倒

华为机试HJ11数字颠倒 题目&#xff1a; 输入一个整数&#xff0c;将这个整数以字符串的形式逆序输出 程序不考虑负数的情况&#xff0c;若数字含有0&#xff0c;则逆序形式也含有0&#xff0c;如输入为100&#xff0c;则输出为001 想法&#xff1a; 遍历输出倒叙输出 inp…

NeoVim在VSCode上进行多行编辑

多行编辑可以工作在可视行模式和可视块模式&#xff0c;插入模式下还没有实现出来 在可视行或者块模式下&#xff0c;选择需要编辑的行&#xff0c;然后按ma/mA或者mi/mI进入编辑模式&#xff0c;组合键之间时间尽量短&#xff0c;不同的组合键影响如下&#xff1a; 对于可视…

浏览器向客户端提供文件下载(Java实现)

场景&#xff1a; 某一系统需上传黑白名单时&#xff0c;需向用户提供导入模板&#xff0c;这时候需要为客户端提供文件模板下载&#xff0c;用户按照该模板格式填写内容。 package com.wyw.learn.upOrdownload.service;import lombok.RequiredArgsConstructor; import org.spr…

springboot课堂考勤系统-计算机毕业设计源码94408

目 录 1 绪论 1.1 选题背景和意义 1.2国内外研究现状 1.3论文结构与章节安排 2 课堂考勤系统系统分析 2.1 可行性分析 2.1.1技术可行性分析 2.1.2 操作可行性分析 2.1.3 法律可行性分析 2.2 系统功能分析 2.2.1 功能性分析 2.3 系统用例分析 2.4 系统流程分析 2.…

免费的鼠标连点器哪个好用?5款2024年最新鼠标连点器分享

鼠标连点器是电脑网络游戏爱好者并不陌生的游戏辅助工具&#xff0c;他在FPS、RTS、moba等游戏种类中发挥着重要作用。可以帮助玩家的鼠标完成各种简单点击动作。轻松实现游戏刷机升级。让你游戏升级不再“肝”&#xff0c;轻松刷图升级&#xff0c;秒表大佬不是梦&#xff01;…

使用getline()从文件中读取一行字符串

我们知道&#xff0c;getline() 方法定义在 istream 类中&#xff0c;而 fstream 和 ifstream 类继承自 istream 类&#xff0c;因此 fstream 和 ifstream 的类对象可以调用 getline() 成员方法。 当文件流对象调用 getline() 方法时&#xff0c;该方法的功能就变成了从指定文件…

电传动无杆飞机牵引车交付用户

自2019年起&#xff0c;我们计划做电传动控制&#xff0c;先后做了电传动水泥搅拌罐车罐体控制&#xff08;国内首创&#xff09;&#xff0c;初步理解了电机控制的特点。 20-21年接着做了10t飞机牵引车控制&#xff0c;还是电液控制联合的&#xff0c;把越野叉车的行驶控制方…

Blender渲染慢?那是你还不知道这5个技巧

Blender是一款功能强大且用途广泛的软件&#xff0c;可帮助 3D 艺术家和动画师创作出色的视觉内容。如果您使用过 Blender&#xff0c;您就会知道渲染可能非常耗时。渲染时间过长可能会令人烦恼并限制创造力。 在这篇文章中&#xff0c;我们将提供一些专家提示和想法以加快 Bl…

一文搞懂MySsql的Buffer Pool

Buffer Pool是什么 Buffer Pool是MySQL数据库中一个非常关键的组件。数据库中的数据最终都是存放在磁盘文件上的。但是在对数据库执行增删改查操作时&#xff0c;不可能直接更新磁盘上的数据。因为如果直接对磁盘进行随机读写操作&#xff0c;那速度是相当的慢的。随便一个大磁…

软考(高项)系统分析师--论文写作技巧

文章目录 前言一、论文的结构和要求&#xff1a;1.1 论文的结构&#xff1a;1.2 论文的要求&#xff1a; 二、论文每段的写法&#xff1a;2.1 解题&#xff1a;2.2 摘要&#xff1a;2.2.1 第一段&#xff1a;2.2.2 第二段&#xff1a; 2.3 正文&#xff1a;2.3.1 项目背景&…

TikTok美区日销二十万美金爆款黑马!胸贴赛道成功起飞!

从去年开始&#xff0c;一项名为“No bra”&#xff08;无胸罩&#xff09;的挑战就长期刷屏TikTok。随着平台内各大博主和明星站台发声&#xff0c;越来越多用户也参与其中&#xff0c;话题的热度逐渐走向高潮。截止到目前&#xff0c; TikTok上相关话题累计播放量已高达8.3亿…

Eureka从入门到精通面试题及答案参考

什么是Eureka?它在微服务架构中扮演什么角色? Eureka是Netflix开源的一款基于REST的服务发现组件,它主要应用于构建分布式系统中的服务注册与发现。在微服务架构中,Eureka扮演着至关重要的角色,它让微服务架构中的各个服务实例能够互相发现、相互调用,从而实现了服务之间…

ubuntu 22.04配置国内镜像源-2024.7月最新版

Ubuntu 22.04 LTS这是一个长期支持版本&#xff0c;它将被支持五年&#xff0c;直到2027年4月。已发布的LTS版本带来了一些新的功能 国内有很多Ubuntu 22.04的镜像源&#xff0c;包括阿里、网易&#xff0c;还有很多教育网的镜像源&#xff0c;比如清华源、中科大源。 在教程中…

django admin添加自己的页面

建立模型 如果要单独建一个页面&#xff0c;用于展示model的数据&#xff0c;可以新建一个model&#xff0c;继承自要展示的那个类 class ViewsByDayModel(ViewsByDay): # 父类为要展示的model类class Meta:proxy True # 使用代理verbose_name 每日浏览次数统计verbose_nam…

仿Antd-mobile的Cascader实现省市区联动

为啥不直接用Cascader 级联选择组件呢&#xff1f;主要是因为作为老项目&#xff0c;已经引入了antd-mobile2.3.4&#xff0c;同时引入v5版本会有兼容性问题。 原始数据格式&#xff1a; 首先需要将后端返回的数据转为前端定义的格式&#xff0c;方便使用&#xff1a; [{&qu…

第二十六章 生成器(generator)(Python)

文章目录 前言一、生成器函数 前言 在 Python 中&#xff0c;使用了 yield 的函数被称为生成器&#xff08;generator&#xff09; yield 是一个关键字&#xff0c;用于定义生成器函数&#xff0c;生成器函数是一种特殊的函数&#xff0c;可以在迭代过程中逐步产生值&#xff…

jsqlparse工具拦截sql处理和拓展

前置知识 访问者模式 &#xff08;Visitor Pattern&#xff09;是一种行为设计模式&#xff0c;它允许你定义在不改变被访问元素的类的前提下&#xff0c;扩展其功能。通过将操作&#xff08;操作或算法&#xff09;从对象结构中提取出来&#xff0c;可以在不修改这些对象的前…

网络扫描工具Nmap,用于发现网络中的主机和服务

Nmap 是一个非常强大的网络扫描工具&#xff0c;可以用于发现网络中的主机和服务。使用 Nmap 可以获取关于目标系统的各种信息&#xff0c;包括开放端口、操作系统版本、服务版本等。以下是如何使用 Nmap 以及一些常见的用例&#xff1a; 安装 Nmap 在 Ubuntu 22.04 上安装 N…