C++17更多新特性
800 Words | Read in about 4 Min | View times
Overview
C++17新增了不少新特性,重点部分我们在前几篇文章《C++17类模板参数推导》、《C++17结构化绑定》、《C++17 if-switch语句初始化》、《C++17折叠表达式》、《C++17 string_view》、《C++17 constexpr的改进》中都有介绍,剩下的新特性在这篇文章中集中介绍。
本系列文章将包括以下领域:
本章其他内容请见 《现代C++》
内联变量
C++17之前只有内联函数,C++17开始新增内联变量。在C++17之前,类中的静态变量是不能在头文件中初始化的,需要在源文件里面进行初始化。有了内联变量之后,就可以在头文件中进行初始化了:
1//C++17之前
2//头文件
3struct Foo {
4 static const int val;
5};
6//源文件
7inline const int Foo::val = 1;
8
9//C++17
10struct Bar {
11 inline static const int val = 1;
12};
namespace嵌套
namespace嵌套是对复杂命名空间的简化:
1//C++17之前
2namespace A {
3 namespace B {
4 namespace C {
5 void foo();
6 }
7 }
8}
9
10//C++17
11namespace A::B::C {
12 void foo();
13}
lambda表达式用*this捕获对象副本
在lambda表达式的捕获列表中,我们往往通过捕获this
指针来访问类中的成员,但是这里捕获的是this
指针,指向对象的引用,正常情况下没什么问题。但是在多线程环境下,就会出问题了。函数的作用域超出对象的作用域,对象被析构,再通过this
访问类成员就会出问题:
1struct Foo {
2 int val;
3 void bar() {
4 auto f = [this]() { std::cout << val << std::endl; };
5 f();
6 }
7};
8
9int main() {
10 Foo foo;
11 foo.bar();
12 return 0;
13}
C++17对lambda进行进一步优化,支持捕获*this
,不持有this
指针,而是持有对象的拷贝,这样lambda中的生命期就与对象的生命期没有关系了,可以用来解决多线程中的生命期问题:
1struct Foo {
2 int val;
3 void bar() {
4 auto f = [*this]() { std::cout << val << std::endl; }; //注意这里*this,不是this
5 f();
6 }
7};
8
9int main() {
10 Foo foo;
11 foo.bar();
12 return 0;
13}
std::variant
C++17新增了std::variant
来实现类似union
的功能。那么既然有了union
,为何还需要variant
呢?
这是因为union
中只能有基础类型,像std::string
、std::map
等复杂类型是不能出现在union
中的,而std::variant
却可以。
1int main() {
2 std::variant<int, std::string> var("zhxilin");
3 std::cout << var.index() << std::endl; //1
4
5 //通过类型取值
6 auto str1 = std::get<std::string>(var);
7 std::cout << str1 << std::endl; //zhxilin
8 //通过索引取值
9 auto str2 = std::get<1>(var);
10 std::cout << str2 << std::endl; //zhxilin
11
12 try {
13 //通过错误的类型或错误的索引取值时产生std::bad_variant_access异常
14 auto v = std::get<int>(var);
15 //auto v = std::get<0>(var);
16 } catch (std::bad_variant_access) {
17 //...
18 }
19
20 var = 123;
21 std::cout << var.index() << std::endl; //0
22
23 return 0;
24}
std::variant
提供index()
函数用来返回当前生效的类型的索引。另外可以通过std::get<>
来获取相应的值。
一般情况下第一个std::variant
的类型需要有默认构造函数,否则将编译失败:
1struct Foo {
2 Foo(int v) {}
3};
4
5int main() {
6 std::variant<Foo, int> var; //error: no matching constructor for initialization of 'std::variant<Foo, int>'
7 return 0;
8}
解决这个问题可以通过使用std::monostate
来打桩占位,模拟一个空状态:
1std::variant<std::monostate, Foo, int> var; //编译通过,这样通过index()获取的索引永远不会为0
std::optional
C++17新增了std::optional
可以用来返回空值,避免了使用智能指针并管理内存的麻烦:
1#include <iostream>
2
3std::optional<int> StoI(const std::string& str) {
4 try {
5 return std::stoi(str);
6 } catch(...) {
7 return std::nullopt;
8 }
9}
10
11int main() {
12 std::string str{ "zhxilin" };
13 std::optional<int> i = StoI(str);
14 if (i)
15 std::cout << *i << std::endl;
16 else
17 std::cout << "invalid" << std::endl;
18
19 return 0;
20}
std::any
C++17引入了std::any
和std::any_cast<>
,包含在标准库头文件<any>
中,可以用来存储任意类型的单个数值,并进行类型转换:
1#include <iostream>
2#include <any>
3
4int main() {
5 std::any foo = 1;
6 if (foo.has_value())
7 std::cout << foo.type().name() << ": " << std::any_cast<int>(foo) << std::endl; //i: 1
8
9 foo = 3.14f;
10 if (foo.has_value())
11 std::cout << foo.type().name() << ": " << std::any_cast<float>(foo) << std::endl; //f: 3.14
12
13 foo.reset(); //重置数据,取消存储
14 if (foo.has_value())
15 std::cout << foo.type().name() << std::endl;
16 return 0;
17}
std::apply
C++17引入std::apply
,定义在标准库头文件<tuple>
中,可以将类元组std::tuple
、std::pair
等作为函数参数进行传递,其原型声明为:
1template<class F, class Tuple>
2constexpr decltype(auto) apply(F&& f, Tuple&& t);
其中f
是可调用对象,t
是一个元组类型,其元素将作为f
的参数进行传递,返回值是f
的返回结果。
std::apply
的源码实现如下:
1namespace detail {
2template<class F, class Tuple, std::size_t... I>
3constexpr decltype(auto) apply_impl(F&& f, Tuple&& t, std::index_sequence<I...>)
4{
5 return std::invoke(std::forward<F>(f), std::get<I>(std::forward<Tuple>(t))...);
6}
7} // namespace detail
8
9template<class F, class Tuple>
10constexpr decltype(auto) apply(F&& f, Tuple&& t)
11{
12 return detail::apply_impl(
13 std::forward<F>(f), std::forward<Tuple>(t),
14 std::make_index_sequence<std::tuple_size_v<std::remove_reference_t<Tuple>>>{});
15}
使用方法如下:
1#include <iostream>
2#include <tuple>
3#include <utility>
4
5int add(int first, int second) {
6 return first + second;
7}
8
9template<typename T>
10T add_generic(T first, T second) {
11 return first + second;
12}
13
14auto add_lambda = [](auto first, auto second) {
15 return first + second;
16};
17
18template<typename... Ts>
19std::ostream& operator<<(std::ostream& os, std::tuple<Ts...> const& theTuple)
20{
21 auto f = [&os](Ts const&... tupleArgs)
22 {
23 os << '[';
24 std::size_t n{0};
25 ((os << tupleArgs << (++n != sizeof...(Ts) ? ", " : "")), ...);
26 os << ']';
27 };
28 std::apply(f, theTuple);
29 return os;
30}
31
32int main()
33{
34 std::cout << std::apply(add, std::pair(1, 2)) << std::endl; //3
35
36 //error: no matching function for call to 'apply'
37 //std::cout << std::apply(add_generic, std::make_pair(2.0f, 3.0f)) << std::endl;
38
39 std::cout << std::apply(add_lambda, std::pair(2.0f, 3.0f)) << std::endl; //5
40
41 std::tuple foo(30, "zhxilin", 3.14f, 'o'); //[30, zhxilin, 3.14, o]
42 std::cout << foo << std::endl;
43
44 return 0;
45}
std::make_from_tuple
C++17引入std::make_from_tuple
,可以将std::tuple
展开作为构造函数的参数:
1struct Foo {
2 Foo(int first, float second, int third) {
3 std::cout << first << ", " << second << ", " << third << std::endl;
4 }
5};
6
7int main() {
8 auto tuple = std::make_tuple(25, 3.14f, 10);
9 std::make_from_tuple<Foo>(std::move(tuple));
10
11 return 0;
12}
std::as_const
C++17引入std::as_const
,可以将左值转成const
类型:
1std::string s1 = "zhxilin";
2const std::string& s2 = std::as_const(s1);
std::file_system
C++17正式将std::file_system
纳入标准库中,定义在头文件<filesystem>
中,提供了一系列文件的基本操作。更多关于文件系统的操作可以参考cppreference。