他来了,他来了,C++17新特性精华都在这了

开发 后端
今天向亲爱的读者们介绍下C++17的新特性,现在基本上各个编译器对C++17都已经提供完备的支持,建议大家编程中尝试使用下C++17,可以一定程度上简化代码编写,提高编程效率。

[[334546]]

本文转载自微信公众号「程序喵大人」,作者程序喵大人 。转载本文请联系程序喵大人公众号。

程序喵之前已经介绍过C++11的新特性和C++14的新特性(点击对应文字,直接访问),今天向亲爱的读者们介绍下C++17的新特性,现在基本上各个编译器对C++17都已经提供完备的支持,建议大家编程中尝试使用下C++17,可以一定程度上简化代码编写,提高编程效率。

主要新特性如下:

 

  • 构造函数模板推导
  • 结构化绑定
  • if-switch语句初始化
  • 内联变量
  • 折叠表达式
  • constexpr lambda表达式
  • namespace嵌套
  • __has_include预处理表达式
  • 在lambda表达式用*this捕获对象副本
  • 新增Attribute
  • 字符串转换
  • std::variant
  • std::optional
  • std::any
  • std::apply
  • std::make_from_tuple
  • as_const
  • std::string_view
  • file_system
  • std::shared_mutex

下面,程序喵一一介绍:

构造函数模板推导

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

  1. pair<intdouble> p(1, 2.2); // before c++17 

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

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

结构化绑定

通过结构化绑定,对于tuple、map等类型,获取相应值会方便很多,看代码:

  1. std::tuple<intdouble> func() { 
  2.    return std::tuple(1, 2.2); 
  3.  
  4. int main() { 
  5.    auto[i, d] = func(); //是C++11的tie吗?更高级 
  6.    cout << i << endl; 
  7.    cout << d << endl; 
  8.  
  9. //========================== 
  10. void f() { 
  11.    map<int, string> m = { 
  12.     {0, "a"}, 
  13.     {1, "b"},   
  14.   }; 
  15.    for (const auto &[i, s] : m) { 
  16.        cout << i << " " << s << endl; 
  17.   } 
  18.  
  19. // ==================== 
  20. int main() { 
  21.    std::pair a(1, 2.3f); 
  22.    auto[i, f] = a; 
  23.    cout << i << endl; // 1 
  24.    cout << f << endl; // 2.3f 
  25.    return 0; 

结构化绑定还可以改变对象的值,使用引用即可:

  1. // 进化,可以通过结构化绑定改变对象的值 
  2. int main() { 
  3.    std::pair a(1, 2.3f); 
  4.    auto& [i, f] = a; 
  5.    i = 2; 
  6.    cout << a.first << endl; // 2 

注意结构化绑定不能应用于constexpr

  1. constexpr auto[x, y] = std::pair(1, 2.3f); // compile error, C++20可以 

结构化绑定不止可以绑定pair和tuple,还可以绑定数组和结构体等。

  1. int array[3] = {1, 2, 3}; 
  2. auto [a, b, c] = array; 
  3. cout << a << " " << b << " " << c << endl; 
  4.  
  5. // 注意这里的struct的成员一定要是public的 
  6. struct Point { 
  7.    int x; 
  8.    int y; 
  9. }; 
  10. Point func() { 
  11.    return {1, 2}; 
  12. const auto [x, y] = func(); 

这里其实可以实现自定义类的结构化绑定,代码如下:

  1. // 需要实现相关的tuple_size和tuple_element和get<N>方法。 
  2. class Entry { 
  3. public
  4.    void Init() { 
  5.        name_ = "name"
  6.        age_ = 10; 
  7.   } 
  8.  
  9.    std::string GetName() const { return name_; } 
  10.    int GetAge() const { return age_; } 
  11. private: 
  12.    std::string name_; 
  13.    int age_; 
  14. }; 
  15.  
  16. template <size_t I> 
  17. auto get(const Entry& e) { 
  18.    if constexpr (I == 0) return e.GetName(); 
  19.    else if constexpr (I == 1) return e.GetAge(); 
  20.  
  21. namespace std { 
  22.    template<> struct tuple_size<Entry> : integral_constant<size_t, 2> {}; 
  23.    template<> struct tuple_element<0, Entry> { using type = std::string; }; 
  24.    template<> struct tuple_element<1, Entry> { using type = int; }; 
  25.  
  26. int main() { 
  27.    Entry e; 
  28.    e.Init(); 
  29.    auto [name, age] = e; 
  30.    cout << name << " " << age << endl; // name 10 
  31.    return 0; 

if-switch语句初始化

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

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

C++17之后可以这样:

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

使用这种方式可以尽可能约束作用域,让代码更简洁,但是可读性略有下降。

内联变量

C++17前只有内联函数,现在有了内联变量,我们印象中C++类的静态成员变量在头文件中是不能初始化的,但是有了内联变量,就可以达到此目的:

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

折叠表达式

C++17引入了折叠表达式使可变参数模板编程更方便:

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

constexpr lambda表达式

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

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

注意

constexpr函数有如下限制:

函数体不能包含汇编语句、goto语句、label、try块、静态变量、线程局部存储、没有初始化的普通变量,不能动态分配内存,不能有new delete等,不能虚函数。

namespace嵌套

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

__has_include预处理表达式

可以判断是否有某个头文件,代码可能会在不同编译器下工作,不同编译器的可用头文件有可能不同,所以可以使用此来判断:

  1. #if defined __has_include 
  2. #if __has_include(<charconv>) 
  3. #define has_charconv 1 
  4. #include <charconv> 
  5. #endif 
  6. #endif 
  7.  
  8. std::optional<int> ConvertToInt(const std::string& str) { 
  9.    int value{}; 
  10. #ifdef has_charconv 
  11.    const auto last = str.data() + str.size(); 
  12.    const auto res = std::from_chars(str.data(), last, value); 
  13.    if (res.ec == std::errc{} && res.ptr == lastreturn value; 
  14. #else 
  15.    // alternative implementation... 
  16.    其它方式实现 
  17. #endif 
  18.    return std::nullopt; 

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

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

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

所以C++17增加了新特性,捕获*this,不持有this指针,而是持有对象的拷贝,这样生命周期就与对象的生命周期不相关啦。

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

新增Attribute

我们可能平时在项目中见过__declspec__, __attribute__ , #pragma指示符,使用它们来给编译器提供一些额外的信息,来产生一些优化或特定的代码,也可以给其它开发者一些提示信息。

例如:

  1. struct A { short f[3]; } __attribute__((aligned(8))); 
  2.  
  3. void fatal() __attribute__((noreturn)); 

在C++11和C++14中有更方便的方法:

  1. [[carries_dependency]] 让编译期跳过不必要的内存栅栏指令 
  2. [[noreturn]] 函数不会返回 
  3. [[deprecated]] 函数将弃用的警告 
  4.  
  5. [[noreturn]] void terminate() noexcept; 
  6. [[deprecated("use new func instead")]] void func() {} 

C++17又新增了三个:

[[fallthrough]]:用在switch中提示可以直接落下去,不需要break,让编译期忽略警告

  1. switch (i) {} 
  2.     case 1: 
  3.         xxx; // warning 
  4.     case 2: 
  5.         xxx; 
  6.         [[fallthrough]];      // 警告消除 
  7.     case 3: 
  8.         xxx; 
  9.        break; 

使得编译器和其它开发者都可以理解开发者的意图。

[[nodiscard]] :表示修饰的内容不能被忽略,可用于修饰函数,标明返回值一定要被处理

  1. [[nodiscard]] int func(); 
  2. void F() { 
  3.     func(); // warning 没有处理函数返回值 

[[maybe_unused]] :提示编译器修饰的内容可能暂时没有使用,避免产生警告

  1. void func1() {} 
  2. [[maybe_unused]] void func2() {} // 警告消除 
  3. void func3() { 
  4.     int x = 1; 
  5.     [[maybe_unused]] int y = 2; // 警告消除 

字符串转换

新增from_chars函数和to_chars函数,直接看代码:

  1. #include <charconv> 
  2.  
  3. int 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 

注意

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

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

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

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

std::optional

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

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

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

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

std::any

C++17引入了any可以存储任何类型的单个值,见代码:

  1. int 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; 

std::apply

使用std::apply可以将tuple展开作为函数的参数传入,见代码:

  1. int add(int firstint second) { return first + second; } 
  2.  
  3. auto add_lambda = [](auto first, auto second) { return first + second; }; 
  4.  
  5. int 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'

std::make_from_tuple

使用make_from_tuple可以将tuple展开作为构造函数参数

  1. struct Foo { 
  2.     Foo(int firstfloat secondint third) { 
  3.         std::cout << first << ", " << second << ", " << third << "\n"
  4.     } 
  5. }; 
  6. int main() { 
  7.    auto tuple = std::make_tuple(42, 3.14f, 0); 
  8.    std::make_from_tuple<Foo>(std::move(tuple)); 

std::string_view

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

  1. void func(std::string_view stv) { cout << stv << endl; } 
  2.  
  3. int 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; 

as_const

C++17使用as_const可以将左值转成const类型

  1. std::string str = "str"
  2. const std::string& constStr = std::as_const(str); 

file_system

C++17正式将file_system纳入标准中,提供了关于文件的大多数功能,基本上应有尽有,这里简单举几个例子:

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

std::shared_mutex

C++17引入了shared_mutex,可以实现读写锁,具体可以见我上一篇文章:C++14新特性的所有知识点全在这儿啦!

关于C++17的介绍就到这里,希望对大家有所帮助~

参考资料

https://en.cppreference.com/w/cpp/utility/make_from_tuple

https://en.cppreference.com/w/cpp/utility/apply

https://en.cppreference.com/w/cpp/17

https://cloud.tencent.com/developer/article/1383177

 

https://www.jianshu.com/p/9b8eeddbf1e4

 

 

责任编辑:武晓燕 来源: 程序喵大人
相关推荐

2019-11-06 16:33:29

Ignite微软技术

2024-05-08 08:50:39

React19模式UI

2020-07-27 10:40:35

C++11语言代码

2024-04-28 09:01:06

React 19更新前端

2020-10-14 15:00:38

Python 开发编程语言

2023-12-18 10:11:36

C++17C++代码

2024-02-04 15:58:53

C++ 17编程代码

2023-11-09 08:46:24

2021-09-28 10:37:50

LayUI JDK

2020-04-13 17:17:28

MySQL8.0功能

2023-11-15 20:51:18

TypeScript前端

2023-12-02 08:55:18

Paru 2.0

2021-07-15 08:55:17

ES12 ECMAScript JS 功能

2015-11-12 09:27:13

C++最新进展

2012-05-18 14:36:50

Fedora 17桌面环境

2022-05-02 09:21:25

微信微信支付

2019-06-21 15:30:30

华为辞职消费者

2024-04-03 08:47:58

React服务端组件Actions

2018-02-26 09:08:19

企业存储趋势

2018-12-09 16:18:38

物联网无线技术通信
点赞
收藏

51CTO技术栈公众号