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

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

程序喵之前已经介绍过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前构造一个模板类对象需要指明类型:

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

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

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

结构化绑定

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

std::tuple<int, double> func() {     return std::tuple(1, 2.2); } int main() {     auto[i, d] = func(); //是C++11的tie吗?更高级    cout << i << endl;    cout << d << endl; } //========================== void f() {     map<int, string> m = {      { 0, "a"},     { 1, "b"},     };    for (const auto &[i, s] : m) {         cout << i << " " << s << endl;   } } // ==================== int main() {     std::pair a(1, 2.3f);    auto[i, f] = a;    cout << i << endl; // 1    cout << f << endl; // 2.3f    return 0; } 

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

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

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

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

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

int array[3] = { 1, 2, 3}; auto [a, b, c] = array; cout << a << " " << b << " " << c << endl; // 注意这里的struct的成员一定要是public的亿华云 struct Point {     int x;    int y; }; Point func() {     return { 1, 2}; } const auto [x, y] = func(); 

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

// 需要实现相关的tuple_size和tuple_element和get<N>方法。 class Entry {  public:    void Init() {         name_ = "name";        age_ = 10;   }    std::string GetName() const {  return name_; }    int GetAge() const {  return age_; } private:    std::string name_;    int age_; }; template <size_t I> auto get(const Entry& e) {     if constexpr (I == 0) return e.GetName();    else if constexpr (I == 1) return e.GetAge(); } namespace std {     template<> struct tuple_size<Entry> : integral_constant<size_t, 2> { };    template<> struct tuple_element<0, Entry> {  using type = std::string; };    template<> struct tuple_element<1, Entry> {  using type = int; }; } int main() {     Entry e;    e.Init();    auto [name, age] = e;    cout << name << " " << age << endl; // name 10    return 0; } 

if-switch语句初始化

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

int a = GetValue(); if (a < 101) {     cout << a; } 

C++17之后可以这样:

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

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

内联变量

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

// header file struct A {     static const int value;   }; inline int const A::value = 10; // ==========或者======== struct A {     inline static const int value = 10; } 

折叠表达式

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

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

constexpr lambda表达式

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

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

注意

constexpr函数有如下限制:

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

namespace嵌套

namespace A {     namespace B {         namespace C {             void func();       }   } } // c++17,更方便更舒适 namespace A::B::C {     void func();) } 

__has_include预处理表达式

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

#if defined __has_include #if __has_include(<charconv>) #define has_charconv 1 #include <charconv> #endif #endif std::optional<int> ConvertToInt(const std::string& str) {     int value{ }; #ifdef has_charconv    const auto last = str.data() + str.size();    const auto res = std::from_chars(str.data(), last, value);    if (res.ec == std::errc{ } && res.ptr == last) return value; #else    // alternative implementation...    其它方式实现 #endif    return std::nullopt; } 

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

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

struct A {     int a;    void func() {         auto f = [this] {             cout << a << endl;       };        f();   }   }; int main() {     A a;    a.func();    return 0; } 

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

struct A {     int a;    void func() {         auto f = [*this] {  // 这里            cout << a << endl;       };        f();   }   }; int main() {     A a;    a.func();    return 0; } 

新增Attribute

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

例如:

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

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

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

C++17又新增了三个:

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

switch (i) { }     case 1:         xxx; // warning     case 2:         xxx;         [[fallthrough]];      // 警告消除     case 3:         xxx;        break; } 

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

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

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

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

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

字符串转换

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

#include <charconv> int main() {      const std::string str{ "123456098"};     int value = 0;     const auto res = std::from_chars(str.data(), str.data() + 4, value);     if (res.ec == std::errc()) {          cout << value << ", distance " << res.ptr - str.data() << endl;     } else if (res.ec == std::errc::invalid_argument) {          cout << "invalid" << endl;     }     str = std::string("12.34);     double val = 0;     const auto format = std::chars_format::general;     res = std::from_chars(str.data(), str.data() + str.size(), value, format);     str = std::string("xxxxxxxx");     const int v = 1234;     res = std::to_chars(str.data(), str.data() + str.size(), v);     cout << str << ", filled " << res.ptr - str.data() << " characters \n";     // 1234xxxx, filled 4 characters } 

注意

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

struct A {      A(int i){ } }; int main() {      std::variant<A, int> var; // 编译失败 } 

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

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

std::optional

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

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

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

std::optional<int> StoI(const std::string &s) {      try {          return std::stoi(s);     } catch(...) {          return std::nullopt;     } } void func() {      std::string s{ "123"};     std::optional<int> o = StoI(s);     if (o) {          cout << *o << endl;     } else {          cout << "error" << endl;     } } 

std::any

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

int main() {  // c++17可编译     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();     }     a.reset();     if (a.has_value()) {          cout << a.type().name();     }     a = std::string("a");     cout << a.type().name() << " " << std::any_cast<std::string>(a) << endl;     return 0; } 

std::apply

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

int add(int first, int second) {  return first + second; } auto add_lambda = [](auto first, auto second) {  return first + second; }; int main() {      std::cout << std::apply(add, std::pair(1, 2)) << \n;     std::cout << add(std::pair(1, 2)) << "\n"; // error     std::cout << std::apply(add_lambda, std::tuple(2.0f, 3.0f)) << \n; } 

std::make_from_tuple

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

struct Foo {      Foo(int first, float second, int third) {          std::cout << first << ", " << second << ", " << third << "\n";     } }; int main() {     auto tuple = std::make_tuple(42, 3.14f, 0);    std::make_from_tuple<Foo>(std::move(tuple)); } 

std::string_view

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

void func(std::string_view stv) {  cout << stv << endl; } int main(void) {      std::string str = "Hello World";     std::cout << str << std::endl;     std::string_view stv(str.c_str(), str.size());     cout << stv << endl;     func(stv);     return 0; } 

as_const

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

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

file_system

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

namespace fs = std::filesystem; fs::create_directory(dir_path); fs::copy_file(src, dst, fs::copy_options::skip_existing); fs::exists(filename); 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

IT科技类资讯
上一篇:互联网其实拼的也是人脉,域名投资也是一个时效性很强的东西,一个不起眼的消息就会引起整个域名投资市场的动荡,因此拓宽自己的人脉圈,完善自己的信息获取渠道,让自己能够掌握更为多样化的信息,这样才更有助于自己的域名投资。
下一篇:网站页面结构改版,仅是页面样式发生变化,不会对排名、收录有影响;只有涉及到页面URL改变,才会对网站排名、收录有影响。