C++17更多新特性

Share on:
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::stringstd::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::anystd::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::tuplestd::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

Prev Post: 『C++17 constexpr的改进』