C++17 新特性
折叠表达式
C++17 中引入了折叠表达式,主要是方便模板编程,分为左右折叠。
语法
(形参包 运算符 ...) (1) (... 运算符 形参包) (2) (形参包 运算符 ... 运算符 初值) (3) (初值 运算符 ... 运算符 形参包) (4)
折叠表达式的实例化按以下方式展开成表达式 e:
1.一元右折叠 (E 运算符 …) 成为 (E1 运算符 (… 运算符 (EN-1 运算符 EN)))
2.一元左折叠 (… 运算符 E) 成为 (((E1 运算符 E2) 运算符 …) 运算符 EN)
3.二元右折叠 (E 运算符 … 运算符 I) 成为 (E1 运算符 (… 运算符 (EN−1 运算符 (EN 运算符 I))))
4.二元左折叠 (I 运算符 … 运算符 E) 成为 ((((I 运算符 E1) 运算符 E2) 运算符 …) 运算符 EN)
(其中 N 是包展开中的元素数量)
template<typename... Args> bool all(Args... args) { return (... && args); } bool b = all(true, true, true, false); //在 all() 中,一元左折叠展开成 //return ((true && true) && true) && false; //b 是 false
C++标准设置
解决方案下面的项目名上右键->属性->配置属性->C/C+±>语言->C++语言标准,更改成自己的想要的版本:
类模板参数推导
类模板实例化时,可以不必显式指定类型,前提是保证类型可以推导:
#include <iostream> using namespace std; template<class T> class ClassTest { public: ClassTest(T, T) {}; }; int main() { auto y = new ClassTest{ 100, 200 }; //分配的类型是 ClassTest<int> return 0; }
auto 占位的非类型模板形参
#include <iostream> using namespace std; template <auto T> void func1() { cout << T << endl; } int main() { func1<100>(); //func1<int>(); return 0; }
编译期 constexpr if 语句
#include <iostream> using namespace std; template <bool ok> constexpr void func2() { //在编译期进行判断,if和else语句不生成代码 if constexpr (ok == true) { //当ok为true时,下面的else块不生成汇编代码 cout << "ok" << endl; } else { //当ok为false时,上面的if块不生成汇编代码 cout << "not ok" << endl; } } int main() { func2<true>(); //输出ok,并且汇编代码中只有 cout << "ok" << endl; func2<false>(); //输出not ok,并且汇编代码中只有 cout << "not ok" << endl; return 0; }
inline 变量
扩展的 inline 用法,使得可以在头文件或者类内初始化静态成员变量。
//mycode.h inline int value = 100; //mycode.cpp class AAA { inline static int value2 = 200; };
结构化绑定
在 C++11 中,如果需要获取 tuple 中元素,需要使用 get<>() 函数或者 tie<> 函数,这个函数可以把 tuple 中的元素值转换为可以绑定到 tie<>() 左值的集合,也就是说需要已分配好的内存去接收,用起来不方便。
int main() { auto student = make_tuple(string{ "Zhangsan" }, 19, string{ "man" }); string name; size_t age; string gender; tie(name, age, gender) = student; cout << name << ", " << age << ", " << gender << endl; //Zhangsan, 19, man return 0; }
C++17 中的结构化绑定,大大方便了类似操作,而且使用引用捕获时,还可以修改捕获对象里面的值,代码也会简洁很多。
int main() { auto student = make_tuple(string{ "Zhangsan" }, 19, string{ "man" }); auto [name, age, gender] = student; cout << name << ", " << age << ", " << gender << endl; return 0; }
if switch 初始化
使用迭代器操作时,可以使代码更紧凑。
unordered_map<string, int> stu1{ {"zhangsan", 18}, {"wangwu", 19} }; //C++11 auto iter = stu1.find("wangwu"); if (iter != stu1.end()) { cout << iter->second << endl; } //C++17 if (auto iter = stu1.find("wangwu"); iter != stu1.end()) { cout << iter->second << endl; }
简化的嵌套命名空间
//C++17之前 namespace A { namespace B { namespace C { void func1() {} } //namespace C } //namespace B } //namespace A //C++17 namespace A::B::C { void func1() {} } //namespace A::B::C
using 声明语句可以声明多个名称
using std::cout, std::cin; • 1
lambda 表达式捕获 *this
一般情况下,lambda 表达式访问类成员变量时需要捕获 this 指针,这个 this 指针指向原对象,即相当于一个引用,在多线程情况下,有可能 lambda 的生命周期超过了对象的生命周期,此时对成员变量的访问是未定义的。
因此 C++17 中增加捕获 *this,此时捕获的是对象的副本,也可以理解为只能对原对象进行读操作,没有写权限。
#include <iostream> using namespace std; class ClassTest { public: int num; void func1() { auto lamfunc = [*this]() { cout << num << endl; }; lamfunc(); } }; int main() { ClassTest a; a.num = 100; a.func1(); return 0; }
简化重复命名空间的属性列表
为类型、对象、代码等引入由实现定义的属性。
[[属性]] [[属性1, 属性2, 属性3(实参)]] [[命名空间::属性(实参)]] alignas说明符
正式而言,语法是:
[[属性列表]] (C++11起) [[using 属性命名空间 : 属性列表]] (C++17起)
其中属性列表是由逗号分隔的零或更多个属性的序列(可以以指示包展开的省略号 … 结束)。
标识符 属性命名空间 :: 标识符 标识符 (实参列表) 属性命名空间 :: 标识符 (实参列表)
- 1.简单属性,例如 [[noreturn]]
- 2.有命名空间的属性,例如 [[gnu::unused]]
- 3.有实参的属性,例如 [[deprecated(“because”)]]
- 4.既有命名空间又有实参列表的属性
举个例子:
[[gnu::always_inline]] [[gnu::hot]] [[gnu::const]] [[nodiscard]] inline int f(); //声明 f 带四个属性 //C++11 [[gnu::always_inline, gnu::const, gnu::hot, nodiscard]] int f(); //同上,但使用含有四个属性的单个属性说明符 //C++17: [[using gnu:const, always_inline, hot]] [[nodiscard]] int f [[gnu::always_inline]] (); //属性可出现于多个说明符中 int f() { return 0; }
__has_include
跨平台项目需要考虑不同平台编译器的实现,使用 __has_include 可以判断当前环境下是否存在某个头文件。
int main() { #if __has_include("iostream") cout << "iostream exist." << endl; #endif #if __has_include(<cmath>) cout << "<cmath> exist." << endl; #endif return 0; }
新增属性
[[fallthrough]]
switch 语句中跳到下一条语句,不需要 break,让编译器忽略告警。
int i = 1; int result; switch (i) { case 0: result = 1; //warning case 1: result = 2; [[fallthrough]]; //no warning default: result = 0; break; }
[[nodiscard]]
所修饰的内容不可被忽略,主要用于修饰函数返回值。
当用于描述函数的返回值时,如果调用函数的地方没有获取返回值时,编译器会给予警告。
[[nodiscard]] auto func(int a, int b) { return a + b; } int main() { func(2, 3); //警告 return 0; }
[[maybe_unused]]
用于描述暂时没有被使用的函数或变量,以避免编译器对此发出警告。
[[maybe_unused]] void func() //没有被使用的函数 { cout << "test" << endl; } int main() { [[maybe_unused]] int num = 0; //没有被使用的变量 return 0; }
charconv
<charconv> 是 C++17 新的标准库头文件,包含了相关类和两个转换函数。
可以完成传统的整数/浮点和字符串互相转换的功能(atoi、itoa、atof、sprintf等),同时支持输出格式控制、整数基底设置并且将整数和浮点类型对字符串的转换整合了起来。
是独立于本地环境、不分配、不抛出的。目的是在常见的高吞吐量环境,例如基于文本的交换( JSON 或 XML )中,允许尽可能快的实现。
chars_format
chars_format 是作为格式控制的类定义在 头文件中。
enum class chars_format { scientific = /*unspecified*/, fixed = /*unspecified*/, hex = /*unspecified*/, general = fixed | scientific };
from_chars
first, last - 要分析的合法字符范围。
value - 存储被分析值的输出参数,若分析成功。.
base - 使用的整数基底:2 与 36 间的值(含上下限)。
fmt - 使用的浮点格式,std::chars_format 类型的位掩码。
struct from_chars_result { const char* ptr; std::errc ec; }; std::from_chars_result from_chars(const char* first, const char* last, /*see below*/& value, int base = 10); std::from_chars_result from_chars(const char* first, const char* last, float& value, std::chars_format fmt = std::chars_format::general); std::from_chars_result from_chars(const char* first, const char* last, double& value, std::chars_format fmt = std::chars_format::general); std::from_chars_result from_chars(const char* first, const char* last, long double& value, std::chars_format fmt = std::chars_format::general);
to_chars
struct to_chars_result { char* ptr; std::errc ec; }; std::to_chars_result to_chars(char* first, char* last, value, int base = 10);