参考链接:C++17
一、语言特性
1.1 折叠表达式
C++17中引入了折叠表达式,主要是方便模板编程,分为左右折叠,下图为其解包形式:
template <typename... Args> auto sub_right(Args... args) {
return (args - ...);
}
template <typename... Args> auto sub_left(Args... args) { return (... - args); }
template <typename... Args> auto sum_right(Args... args) {
return (args + ...);
}
int main() {
std::cout << sub_right(8, 4, 2) << std::endl; // (8 - (4 - 2)) = 6
std::cout << sub_left(8, 4, 2) << std::endl; // ((8 - 4) - 2) = 2
std::cout << sum_right(std::string{"hello "}, std::string{"world"})
<< std::endl; // hello world
return 0;
}
1.2 类模板参数推导
类模板实例化时,可以不必显式指定类型,前提是保证类型可以推导:
template <typename T> struct A { A(T, T); };
int main() {
std::pair p(2, 4.5); // std::pair<int, double> p
std::tuple t(4, 3, 2.5); // std::tuple<int, int, double> t
std::less l; // std::less<void> l
auto y = new A{1, 2}; // A<int>::A(int, int)
return 0;
}
1.3 auto占位的非类型模板形参
template <auto T> void foo() { std::cout << T << std::endl; }
int main() {
foo<100>();
// foo<int>();
return 0;
}
1.4 编译期constexpr if语句
template <bool ok> constexpr void foo() {
//在编译期进行判断,if和else语句不生成代码
if constexpr (ok == true) {
//当ok为true时,下面的else块不生成汇编代码
std::cout << "ok" << std::endl;
} else {
//当ok为false时,上面的if块不生成汇编代码
std::cout << "not ok" << std::endl;
}
}
int main() {
foo<true>(); //输出ok,并且汇编代码中只有 std::cout << "ok" << std::endl;
foo<false>(); //输出not ok,并且汇编代码中只有 std::cout << "not ok" << std::endl;
return 0;
}
1.5 constexpr的lambda表达式
lambda表达式可以在编译期进行运算,且函数体不能包含汇编语句、goto语句、label、try块、静态变量、线程局部存储、没有初始化的普通变量,不能动态分配内存,不能有new delete等,不能为虚函数。
int main() {
constexpr auto foo = [](int a, int b) { return a + b; };
static_assert(5 == foo(2, 3), "not compile-time");
return 0;
}
1.6 inline变量
扩展的inline用法,使得可以在头文件或者类内初始化静态成员变量:
// test.h
inline int value = 1;
// test.cpp
struct A {
inline static int val = 1;
};
1.7 结构化绑定
在C++11中,如果需要获取tuple中元素,需要使用get<>()
函数或者tie<>
函数,这个函数可以把tuple中的元素值转换为可以绑定到tie<>()
左值的集合(也就是说需要已分配好的内存去接收)。用起来不甚方便:
auto student = std::make_tuple(std::string{"YongDu"}, 26, std::string{"man"});
std::string name;
size_t age;
std::string gender;
std::tie(name, age, gender) = student;
std::cout << name << ", " << age << ", " << gender << std::endl;
// YongDu, 26, man
C++17中的结构化绑定,大大方便了类似操作,而且使用引用捕获时,还可以修改捕获对象里面的值,代码也会简洁很多:
struct Student {
std::string name;
size_t age;
};
Student getStudent() { return {"dycc", 26}; }
int main() {
auto student = std::make_tuple(std::string{"YongDu"}, 26, std::string{"man"});
auto [name, age, gender] = student;
std::cout << name << ", " << age << ", " << gender << std::endl;
// YongDu, 26, man
std::unordered_map<std::string, size_t> students;
students.emplace(std::make_pair("DuYong", 26));
students.emplace(std::make_pair("YongDu", 26));
for (auto &[name, age] : students) {
std::cout << name << ", " << age << std::endl;
}
auto [_name, _age] = getStudent();
std::cout << _name << ", " << _age << std::endl;
return 0;
}
1.8 if,switch初始化
以前看到这个特性时,觉得很鸡肋,没啥作用,最近在使用迭代器操作时,便想到了这个特性:
// C++11
std::unordered_map<std::string, int> students{{"liBai", 18}, {"hanXin", 19}};
auto iter = students.find("hanXin");
if (iter != students.end()) {
std::cout << iter->second << std::endl;
}
// C++17
if (auto iter = students.find("hanXin"); iter != students.end()) {
std::cout << iter->second << std::endl;
}
可读性一般,但代码更紧凑一点。
1.9 u8-char
1.10 简化的嵌套命名空间
// C++17之前
namespace A {
namespace B {
namespace C {
void foo() {}
} // namespace C
} // namespace B
} // namespace A
// C++17
namespace A::B::C {
void foo() {}
} // namespace A::B::C
1.11 using声明语句可以声明多个名称
using std::cout, std::cin;
1.12 新的求值顺序规则
如下代码片段:
int main() {
std::unordered_map<int, int> hashMap;
hashMap[0] = hashMap.size(); // 此处不确定插入{0, 0},还是{0, 1}
return 0;
}
为了解决类似问题,C++17优化了求值顺序:
- 后缀表达式从左到右求值,包括函数调用和成员选择表达式;
- 赋值表达式从右向左求值,包括复合赋值;
- 从左到右计算移位操作符的操作数。
1.13 强制的复制消除
1.14 lambda表达式捕获 *this
一般情况下,lambda表达式访问类成员变量时需要捕获this指针,这个this指针指向原对象,即相当于一个引用,在多线程情况下,有可能lambda的生命周期超过了对象的生命周期,此时,对成员变量的访问是未定义的。因此C++17中增加捕获*this,此时捕获的是对象的副本,也可以理解为只能对原对象进行读操作,没有写权限。
struct A {
int m_val;
void foo() {
auto lamfoo = [*this]() { cout << m_val << endl; };
lamfoo();
}
};
int main() {
A a;
a.m_val = 1;
a.foo();
return 0;
}
1.15 简化重复命名空间的属性列表
[[gnu::always_inline]] [[gnu::hot]] [[gnu::const]] [[nodiscard]]
inline int f(); // declare f with four attributes
[[gnu::always_inline, gnu::const, gnu::hot, nodiscard]]
int f(); // same as above, but uses a single attr specifier that contains four attributes
// C++17:
[[using gnu : const, always_inline, hot]] [[nodiscard]]
int f[[gnu::always_inline]](); // an attribute may appear in multiple specifiers
int f() { return 0; }
int main(){}
1.16 __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;
}
1.17 新增属性
[[fallthrough]]
switch语句中跳到下一条语句,不需要break,让编译器忽略告警:
int main() {
int i = 1;
int result;
switch (i) {
case 0:
result = 1; // warning
case 1:
result = 2;
[[fallthrough]]; // no warning
default:
result = 0;
break;
}
return 0;
}
[[nodiscard]]
所修饰的内容不可被忽略,主要用于修饰函数返回值:
[[nodiscard]] auto foo(int a, int b) { return a + b; }
int main() {
foo(2, 3); // 放弃具有 "nodiscard" 属性的函数的返回值
return 0;
}
[[maybe_unused]]
void foo1() {}
[[maybe_unused]] void func2() {} // no warning
int main() {
int x = 1;
[[maybe_unused]] int y = 2; // no warning
return 0;
}
二、库相关
库相关特性较多,更多库功能可以参靠:C++17
2.1 字符串转换函数
更方便地处理字符串和数字之间的转换:
#include <charconv>
#include <iostream>
using std::cout, std::endl;
int main() {
std::string str{"123456789"};
int val = 0;
auto res = std::from_chars(str.data(), str.data() + 4, val);
if (res.ec == std::errc()) {
cout << "val: " << val << ", distance: " << res.ptr - str.data() << endl;
// val: 1234, distance: 4
} else if (res.ec == std::errc::invalid_argument) {
cout << "invalid" << endl;
}
str = std::string{"12.34"};
double value = 0;
auto format = std::chars_format::general;
res = std::from_chars(str.data(), str.data() + str.size(), value, format);
cout << "value: " << value << endl;
// value: 12.34
str = std::string{"xxxxxxxx"};
int v = 1234;
auto result = std::to_chars(str.data(), str.data() + str.size(), v);
cout << "str: " << str << ", filled: " << result.ptr - str.data()
<< " characters." << endl;
// str: 1234xxxx, filled: 4 characters.
return 0;
}
2.2 std::variant
C++17中提供了std::variant类型,意为多变的,可变的类型,类似于加强版的union,里面可以存放复合数据类型,且操作元素更为方便。
int main() {
std::variant<int, std::string> var("hello");
cout << var.index() << endl;
var = 123;
cout << var.index() << endl;
try {
var = "world";
std::string str = std::get<std::string>(var); // 通过类型获取
var = 3;
int i = std::get<0>(var); // 通过索引获取
cout << str << ", " << i << endl;
} catch (...) {
}
return 0;
}
2.3 std::optional
该类型主要用于简化函数返回值的判断,自己也没有深入去挖掘,附上一个简单例子:
std::optional<int> StoI(const std::string &str) {
try {
return std::stoi(str);
} catch (...) {
return std::nullopt;
}
}
int main() {
std::string str{"1234"};
std::optional<int> result = StoI(str);
if (result) {
cout << *result << endl;
} else {
cout << "StoI() error." << endl;
}
return 0;
}
2.4 std::any
在C++11中引入的auto自动推导类型变量大大方便了编程,但是auto变量一旦声明,该变量类型不可再改变。C++17中引入了std::any类型,该类型变量可以存储任何类型的值,也可以时刻改变它的类型,类似于python中的变量。
int main() {
std::any a = 1;
cout << a.type().name() << ", " << std::any_cast<int>(a) << endl;
a = 2.2f;
cout << a.type().name() << ", " << std::any_cast<float>(a) << endl;
if (a.has_value()) {
cout << a.type().name() << endl;
}
a.reset();
if (a.has_value()) {
cout << a.type().name();
}
a = std::string("hello");
cout << a.type().name() << ", " << std::any_cast<std::string>(a) << endl;
return 0;
}
2.5 std::apply
将tuple元组解包,并作为函数的传入参数,如下代码:
int add(int a, int b) { return a + b; }
int main() {
auto add_lambda = [](auto a, auto b, auto c) { return a + b + c; };
cout << std::apply(add, std::pair(2, 3)) << endl;
cout << std::apply(add_lambda, std::tuple(2, 3, 4)) << endl;
return 0;
}
2.6 std::make_from_tuple
解包tuple作为构造函数参数构造对象:
struct A {
std::string _name;
size_t _age;
A(std::string name, size_t age) : _name(name), _age(age) { cout << "name: " << _name << ", age: " << _age << endl; }
};
int main() {
auto param = std::make_tuple("kai", 18);
std::make_from_tuple<A>(std::move(param));
return 0;
}
2.7 std::string_view
C++中字符串有两种形式,char*
和std::string
,string类型封装了char*
字符串,让我们对字符串的操作方便了很多,但是会有些许性能的损失,而且由char*
转为string类型,需要调用string类拷贝构造函数,也就是说需要重新申请一片内存,但如果只是对源字符串做只读操作,这样的构造行为显然是不必要的。在C++17中,增加了std::string_view
类型,它通过char*
字符串构造,但是并不会去申请内存重新创建一份该字符串对象,只是char*
字符串的一个视图,优化了不必要的内存操作。相应地,对源字符串只有读权限,没有写权限。
void foo(std::string_view str_v) {
cout << str_v << endl;
return;
}
int main() {
char *charStr = "hello world";
std::string str{charStr};
std::string_view str_v(charStr, strlen(charStr));
cout << "str: " << str << endl;
cout << "str_v: " << str_v << endl;
foo(str_v);
return 0;
}
2.8 std::as_const
将左值转化为const类型
int main() {
std::string str{"hello world"};
cout << std::is_const<decltype(str)>::value << endl;
const std::string str_const = std::as_const(str);
cout << std::is_const<decltype(str_const)>::value << endl;
return 0;
}
2.9 std::filesystem
C++17中引入了filesystem,方便处理文件,提供接口函数很多,用起来也很方便,以下是一个简单的使用例子,详细可参考:Standard library header
int main() {
fs::path str("./data");
if (!fs::exists(str)) {
cout << "path not exist." << endl;
return -1;
}
fs::directory_entry entry(str);
if (entry.status().type() == fs::file_type::directory) {
cout << "it's a directory." << endl;
}
fs::directory_iterator list(str);
for (auto &file : list) {
cout << file.path().filename() << endl;
}
return 0;
}
// it's a directory.
// "text.md"
// "test.cpp"
// "test.txt"
2.10 std::shared_mutex
读写锁相关