C++

C++17特性

Posted by DEVIN on Sun, Jun 18, 2023

构造函数模板推导

在C++17前构造一个模板类对象需要指明类型:

1pair<int, double> p(1, 2.2); // before c++17

C++17就不需要特殊指定,直接可以推导出类型,代码如下:

1pair p(1, 2.2); // c++17 自动推导
2vector v = {1, 2, 3}; // c++17

结构化绑定

1.获取值

 1// 绑定tuple
 2std::tuple<int, double> func() {
 3    return std::tuple(1, 2.2);
 4}
 5
 6int main() {
 7    auto[i, d] = func();
 8    cout << i << endl;
 9    cout << d << endl;
10}
11
12// 绑定map
13void f() {
14    map<int, string> m = {{0, "a"}, {1, "b"}};
15    for (const auto &[i, s] : m) {
16        cout << i << " " << s << endl;
17    }
18}
19
20// 绑定pair
21int main() {
22    std::pair a(1, 2.3f);
23    auto[i, f] = a;
24    cout << i << endl; // 1
25    cout << f << endl; // 2.3f
26    return 0;
27}
28
29// 绑定数组
30int array[3] = {1, 2, 3};
31auto [a, b, c] = array;
32cout << a << " " << b << " " << c << endl;
33
34// 绑定结构体(注意这里的struct的成员一定要是public的)
35struct Point {
36    int x;
37    int y;
38};
39Point func() {
40    return {1, 2};
41}
42const auto [x, y] = func();
 1// 实现自定义类的结构化绑定
 2// 需要实现相关的tuple_size和tuple_element和get<N>方法。
 3class Entry {
 4public:
 5    void Init() {
 6        name_ = "name";
 7        age_ = 10;
 8    }
 9
10    std::string GetName() const { return name_; }
11    int GetAge() const { return age_; }
12private:
13    std::string name_;
14    int age_;
15};
16
17template <size_t I>
18auto get(const Entry& e) {
19    if constexpr (I == 0) return e.GetName();
20    else if constexpr (I == 1) return e.GetAge();
21}
22
23namespace std {
24    template<> struct tuple_size<Entry> : integral_constant<size_t, 2> {};
25    template<> struct tuple_element<0, Entry> { using type = std::string; };
26    template<> struct tuple_element<1, Entry> { using type = int; };
27}
28
29int main() {
30    Entry e;
31    e.Init();
32    auto [name, age] = e;
33    cout << name << " " << age << endl; // name 10
34    return 0;
35}

2.改变值

 1// 可以通过结构化绑定改变对象的值
 2int main() {
 3    std::pair a(1, 2.3f);
 4    auto& [i, f] = a;
 5    i = 2;
 6    cout << a.first << endl; // 2 
 7}
 8
 9// 注意结构化绑定不能应用于constexpr
10constexpr auto[x, y] = std::pair(1, 2.3f); // compile error, C++20可以

if-switch语句初始化

C++17前if语句需要这样写代码:

1int a = GetValue();
2if (a < 101) {
3    cout << a;
4}

C++17之后可以这样:

1// if (init; condition)
2if (int a = GetValue()); a < 101) {
3    cout << a;
4}
5
6string str = "Hi World";
7if (auto [pos, size] = pair(str.find("Hi"), str.size()); pos != string::npos) {
8    std::cout << pos << " Hello, size is " << size;
9}

内联变量(定义静态成员变量)

在头文件中定义类的静态成员变量

 1// a.h
 2struct A {
 3    static const int value;  
 4};
 5inline int const A::value = 10;
 6
 7// 或者
 8struct A {
 9    inline static const int value = 10;
10}

namespace嵌套

 1namespace A {
 2    namespace B {
 3        namespace C {
 4            void func();
 5        }
 6    }
 7}
 8
 9// c++17,更方便更舒适
10namespace A::B::C {
11    void func();)
12}

在lambda表达式用*this捕获对象副本

正常情况下,lambda表达式中访问类的对象成员变量需要捕获this,但是这里捕获的是this指针,指向的是对象的引用,正常情况下可能没问题,但是如果多线程情况下,函数的作用域超过了对象的作用域,对象已经被析构了,还访问了成员变量,就会有问题。

所以C++17增加了新特性,捕获*this,不持有this指针,而是持有对象的拷贝,这样生命周期就与对象的生命周期不相关啦。在并行或异步操作中执行时,按值捕获非常有用,尤其是在某些硬件体系结构(如NUMA)上。

 1struct A {
 2    int a;
 3    void func() {
 4        auto f = [*this] { // 这里
 5            cout << a << endl;
 6        };
 7        f();
 8    }  
 9};
10int main() {
11    A a;
12    a.func();
13    return 0;
14}

constexpr lambda表达式

C++17前lambda表达式只能在运行时使用,C++17引入了constexpr lambda表达式,可以用于在编译期进行计算。

1int main() { // c++17可编译
2    constexpr auto lamb = [] (int n) { return n * n; };
3    static_assert(lamb(3) == 9, "a");
4}

折叠表达式(简化模板)

C++11增加了一个新特性变参模板(variadic template),它可以接受任意个模版参数,参数包不能直接展开,需要通过一些特殊的方法,比如函数参数包的展开可以使用递归方式或者逗号表达式,在使用的时候有点难度。C++17解决了这个问题,通过fold expression(折叠表达式)简化对参数包的展开。C++17 fold expression - 腾讯云

1template <typename ... Ts>
2auto sum(Ts ... ts) {
3    return (ts + ...);
4}
5int a {sum(1, 2, 3, 4, 5)}; // 15
6std::string a{"hello "};
7std::string b{"world"};
8cout << sum(a, b) << endl; // hello world

标准库

std::from_chars/to_chars

 1#include <charconv>
 2
 3int main() {
 4    const std::string str{"123456098"};
 5    int value = 0;
 6    const auto res = std::from_chars(str.data(), str.data() + 4, value);
 7    if (res.ec == std::errc()) {
 8        cout << value << ", distance " << res.ptr - str.data() << endl;
 9    } else if (res.ec == std::errc::invalid_argument) {
10        cout << "invalid" << endl;
11    }
12    str = std::string("12.34);
13    double val = 0;
14    const auto format = std::chars_format::general;
15    res = std::from_chars(str.data(), str.data() + str.size(), value, format);
16    
17    str = std::string("xxxxxxxx");
18    const int v = 1234;
19    res = std::to_chars(str.data(), str.data() + str.size(), v);
20    cout << str << ", filled " << res.ptr - str.data() << " characters \n";
21    // 1234xxxx, filled 4 characters
22}

std::variant

C++17增加std::variant实现类似union的功能,但却比union更高级,举个例子union里面不能有string这种类型,但std::variant却可以,还可以支持更多复杂类型,如map等

 1int main() { // c++17可编译
 2    std::variant<int, std::string> var("hello");
 3    cout << var.index() << endl; // 1
 4    var = 123;
 5    cout << var.index() << endl; // 0
 6
 7    try {
 8        var = "world";
 9        std::string str = std::get<std::string>(var); // 通过类型获取值
10        var = 3;
11        int i = std::get<0>(var); // 通过index获取对应值
12        cout << str << endl; // "world"
13        cout << i << endl; // 3
14    } catch(...) {
15        // xxx;
16    }
17    return 0;
18}

注意:一般情况下variant的第一个类型一般要有对应的构造函数,否则编译失败:

1struct A {
2    A(int i){}  
3};
4int main() {
5    std::variant<A, int> var; // 编译失败
6}

如何避免这种情况呢,可以使用std::monostate来打个桩,模拟一个空状态。

1std::variant<std::monostate, A> var; // 可以编译成功

std::optional

我们有时候可能会有需求,让函数返回一个对象,如下:

1struct A {};
2A func() {
3    if (flag) return A();
4    else {
5        // 异常情况下,怎么返回异常值呢,想返回个空呢
6    }
7}

有一种办法是返回对象指针,异常情况下就可以返回nullptr啦,但是这就涉及到了内存管理,也许你会使用智能指针,但这里其实有更方便的办法就是std::optional。

 1std::optional<int> StoI(const std::string &s) {
 2    try {
 3        return std::stoi(s);
 4    } catch(...) {
 5        return std::nullopt;
 6    }
 7}
 8
 9void func() {
10    std::string s{"123"};
11    std::optional<int> o = StoI(s);
12    if (o) {
13        cout << *o << endl;
14    } else {
15        cout << "error" << endl;
16    }
17}

std::any

C++17引入了any可以存储任何类型的单个值

 1int main() { // c++17可编译
 2    std::any a = 1;
 3    cout << a.type().name() << " " << std::any_cast<int>(a) << endl;
 4    a = 2.2f;
 5    cout << a.type().name() << " " << std::any_cast<float>(a) << endl;
 6    if (a.has_value()) {
 7        cout << a.type().name();
 8    }
 9    a.reset();
10    if (a.has_value()) {
11        cout << a.type().name();
12    }
13    a = std::string("a");
14    cout << a.type().name() << " " << std::any_cast<std::string>(a) << endl;
15    return 0;
16}

std::apply

1int add(int first, int second) { return first + second; }
2
3auto add_lambda = [](auto first, auto second) { return first + second; };
4
5int main() {
6    std::cout << std::apply(add, std::pair(1, 2)) << '\n';
7    std::cout << add(std::pair(1, 2)) << "\n"; // error
8    std::cout << std::apply(add_lambda, std::tuple(2.0f, 3.0f)) << '\n'; // 将tuple展开作为函数的参数传入
9}

std::make_from_tuple

1struct Foo {
2    Foo(int first, float second, int third) {
3        std::cout << first << ", " << second << ", " << third << "\n";
4    }
5};
6int main() {
7   auto tuple = std::make_tuple(42, 3.14f, 0);
8   std::make_from_tuple<Foo>(std::move(tuple)); // 使用make_from_tuple可以将tuple展开作为构造函数参数
9}

std::as_const

1std::string str = "str";
2const std::string& constStr = std::as_const(str); // C++17使用as_const可以将左值转成const类型

Qt中有对应的qAsConst函数:大概意思是非const类型迭代,在多线程中可能会拷贝一份容器的副本?

std::string_view

通常我们传递一个string时会触发对象的拷贝操作,大字符串的拷贝赋值操作会触发堆内存分配,很影响运行效率,有了string_view就可以避免拷贝操作,平时传递过程中传递string_view即可。

 1void func(std::string_view stv) { cout << stv << endl; }
 2
 3int main(void) {
 4    std::string str = "Hello World";
 5    std::cout << str << std::endl;
 6
 7    std::string_view stv(str.c_str(), str.size());
 8    cout << stv << endl;
 9    func(stv);
10    return 0;
11}

std::file_system

1namespace fs = std::filesystem;
2fs::create_directory(dir_path);
3fs::copy_file(src, dst, fs::copy_options::skip_existing);
4fs::exists(filename);
5fs::current_path(err_code);

std::shared_mutex

实现读写锁

新增Attribute

1// C++11引入
2[[carries_dependency]] // 让编译期跳过不必要的内存栅栏指令
3[[noreturn]] // 函数不会返回
4[[deprecated]] // 函数将弃用的警告
5// C++17引入
6[[fallthrough]] // 用在switch中提示不需要break,让编译期忽略警告
7[[nodiscard]] // 表示修饰的内容不能被忽略,可用于修饰函数,标明返回值一定要被处理
8[[maybe_unused]] // 提示编译器修饰的内容可能暂时没有使用,避免产生警告

例子:

 1[[noreturn]] void terminate() noexcept;
 2[[deprecated("use new func instead")]] void func() {}
 3
 4switch (i) {}
 5    case 1:
 6        xxx; // warning
 7    case 2:
 8        xxx; 
 9        [[fallthrough]];      // 警告消除
10    case 3:
11        xxx;
12       break;
13}
14
15[[nodiscard]] int func();
16void F() {
17    func(); // warning 没有处理函数返回值
18}
19
20void func1() {}
21[[maybe_unused]] void func2() {} // 警告消除
22void func3() {
23    int x = 1;
24    [[maybe_unused]] int y = 2; // 警告消除
25}

预处理表达式__has_include

1// 可以判断是否有某个头文件
2#if defined __has_include
3#if __has_include(<charconv>)
4#define has_charconv 1
5#include <charconv>
6#endif
7#endif