C++17新特性总结

参考链接:C++17

一、语言特性

1.1 折叠表达式

C++17中引入了折叠表达式,主要是方便模板编程,分为左右折叠,下图为其解包形式:

image-20210719211140407

template <typename... Args> auto sub_right(Args... args) {
  return (args - ...);
}

template <typename... Args> auto sub_left(Args... args) { return (... - args); }

template <typename... Args> auto sum_right(Args... args) {
  return (args + ...);
}

int main() {
  std::cout << sub_right(8, 4, 2) << std::endl; // (8 - (4 - 2)) = 6
  std::cout << sub_left(8, 4, 2) << std::endl;  // ((8 - 4) - 2) = 2

  std::cout << sum_right(std::string{"hello "}, std::string{"world"})
            << std::endl; // hello world

  return 0;
}

1.2 类模板参数推导

类模板实例化时,可以不必显式指定类型,前提是保证类型可以推导:

template <typename T> struct A { A(T, T); };

int main() {
  std::pair p(2, 4.5);     // std::pair<int, double> p
  std::tuple t(4, 3, 2.5); // std::tuple<int, int, double> t
  std::less l;             // std::less<void> l
  auto y = new A{1, 2};    // A<int>::A(int, int)

  return 0;
}

1.3 auto占位的非类型模板形参

template <auto T> void foo() { std::cout << T << std::endl; }

int main() {
  foo<100>();
  // foo<int>();

  return 0;
}

1.4 编译期constexpr if语句

template <bool ok> constexpr void foo() {
  //在编译期进行判断,if和else语句不生成代码
  if constexpr (ok == true) {
    //当ok为true时,下面的else块不生成汇编代码
    std::cout << "ok" << std::endl;
  } else {
    //当ok为false时,上面的if块不生成汇编代码
    std::cout << "not ok" << std::endl;
  }
}

int main() {
  foo<true>(); //输出ok,并且汇编代码中只有 std::cout << "ok" << std::endl;
  foo<false>(); //输出not ok,并且汇编代码中只有 std::cout << "not ok" << std::endl;
  return 0;
}

1.5 constexpr的lambda表达式

lambda表达式可以在编译期进行运算,且函数体不能包含汇编语句、goto语句、label、try块、静态变量、线程局部存储、没有初始化的普通变量,不能动态分配内存,不能有new delete等,不能为虚函数。

int main() {
  constexpr auto foo = [](int a, int b) { return a + b; };
  static_assert(5 == foo(2, 3), "not compile-time");
  return 0;
}

1.6 inline变量

扩展的inline用法,使得可以在头文件或者类内初始化静态成员变量:

// test.h
inline int value  = 1;
// test.cpp
struct A {
  inline static int val = 1;
};

1.7 结构化绑定

在C++11中,如果需要获取tuple中元素,需要使用get<>()函数或者tie<>函数,这个函数可以把tuple中的元素值转换为可以绑定到tie<>()左值的集合(也就是说需要已分配好的内存去接收)。用起来不甚方便:

auto student = std::make_tuple(std::string{"YongDu"}, 26, std::string{"man"});
std::string name;
size_t age;
std::string gender;
std::tie(name, age, gender) = student;
std::cout << name << ", " << age << ", " << gender << std::endl;
// YongDu, 26, man

C++17中的结构化绑定,大大方便了类似操作,而且使用引用捕获时,还可以修改捕获对象里面的值,代码也会简洁很多:

struct Student {
  std::string name;
  size_t age;
};

Student getStudent() { return {"dycc", 26}; }

int main() {
  auto student = std::make_tuple(std::string{"YongDu"}, 26, std::string{"man"});
  auto [name, age, gender] = student;
  std::cout << name << ", " << age << ", " << gender << std::endl;
  // YongDu, 26, man

  std::unordered_map<std::string, size_t> students;
  students.emplace(std::make_pair("DuYong", 26));
  students.emplace(std::make_pair("YongDu", 26));

  for (auto &[name, age] : students) {
    std::cout << name << ", " << age << std::endl;
  }

  auto [_name, _age] = getStudent();
  std::cout << _name << ", " << _age << std::endl;

  return 0;
}

1.8 if,switch初始化

以前看到这个特性时,觉得很鸡肋,没啥作用,最近在使用迭代器操作时,便想到了这个特性:

// C++11
std::unordered_map<std::string, int> students{{"liBai", 18}, {"hanXin", 19}};
auto iter = students.find("hanXin");
if (iter != students.end()) {
  std::cout << iter->second << std::endl;
}
// C++17
if (auto iter = students.find("hanXin"); iter != students.end()) {
  std::cout << iter->second << std::endl;
}

可读性一般,但代码更紧凑一点。

1.9 u8-char

1.10 简化的嵌套命名空间

// C++17之前
namespace A {
namespace B {
namespace C {
void foo() {}
} // namespace C
} // namespace B
} // namespace A

// C++17
namespace A::B::C {
void foo() {}
} // namespace A::B::C

1.11 using声明语句可以声明多个名称

using std::cout, std::cin;

1.12 新的求值顺序规则

如下代码片段:

int main() {
  std::unordered_map<int, int> hashMap;
  hashMap[0] = hashMap.size(); // 此处不确定插入{0, 0},还是{0, 1}

  return 0;
}

为了解决类似问题,C++17优化了求值顺序:

  • 后缀表达式从左到右求值,包括函数调用和成员选择表达式;
  • 赋值表达式从右向左求值,包括复合赋值;
  • 从左到右计算移位操作符的操作数。

1.13 强制的复制消除

1.14 lambda表达式捕获 *this

一般情况下,lambda表达式访问类成员变量时需要捕获this指针,这个this指针指向原对象,即相当于一个引用,在多线程情况下,有可能lambda的生命周期超过了对象的生命周期,此时,对成员变量的访问是未定义的。因此C++17中增加捕获*this,此时捕获的是对象的副本,也可以理解为只能对原对象进行读操作,没有写权限。

struct A {
  int m_val;
  void foo() {
    auto lamfoo = [*this]() { cout << m_val << endl; };
    lamfoo();
  }
};

int main() {
  A a;
  a.m_val = 1;
  a.foo();

  return 0;
}

1.15 简化重复命名空间的属性列表

[[gnu::always_inline]] [[gnu::hot]] [[gnu::const]] [[nodiscard]]
inline int f(); // declare f with four attributes
 
[[gnu::always_inline, gnu::const, gnu::hot, nodiscard]]
int f(); // same as above, but uses a single attr specifier that contains four attributes
 
// C++17:
[[using gnu : const, always_inline, hot]] [[nodiscard]]
int f[[gnu::always_inline]](); // an attribute may appear in multiple specifiers
 
int f() { return 0; }
 
int main(){}

1.16 __has_include

跨平台项目需要考虑不同平台编译器的实现,使用__has_include可以判断当前环境下是否存在某个头文件:

int main() {
#if __has_include("iostream")
  cout << "iostream exist." << endl;
#endif

#if __has_include(<cmath>)
  cout << "<cmath> exist." << endl;
#endif
  return 0;
}

1.17 新增属性

[[fallthrough]]

switch语句中跳到下一条语句,不需要break,让编译器忽略告警:

int main() {
  int i = 1;
  int result;
  switch (i) {
  case 0:
    result = 1; // warning
  case 1:
    result = 2;
    [[fallthrough]]; // no warning
  default:
    result = 0;
    break;
  }
  return 0;
}
[[nodiscard]]

所修饰的内容不可被忽略,主要用于修饰函数返回值:

[[nodiscard]] auto foo(int a, int b) { return a + b; }

int main() {
  foo(2, 3); // 放弃具有 "nodiscard" 属性的函数的返回值

  return 0;
}
[[maybe_unused]]
void foo1() {}
[[maybe_unused]] void func2() {} // no warning

int main() {
  int x = 1;
  [[maybe_unused]] int y = 2; // no warning
  return 0;
}

二、库相关

库相关特性较多,更多库功能可以参靠:C++17

2.1 字符串转换函数

更方便地处理字符串和数字之间的转换:

#include <charconv>
#include <iostream>
using std::cout, std::endl;

int main() {
  std::string str{"123456789"};
  int val = 0;
  auto res = std::from_chars(str.data(), str.data() + 4, val);
  if (res.ec == std::errc()) {
    cout << "val: " << val << ", distance: " << res.ptr - str.data() << endl;
    // val: 1234, distance: 4

  } else if (res.ec == std::errc::invalid_argument) {
    cout << "invalid" << endl;
  }

  str = std::string{"12.34"};
  double value = 0;
  auto format = std::chars_format::general;
  res = std::from_chars(str.data(), str.data() + str.size(), value, format);
  cout << "value: " << value << endl;
  // value: 12.34

  str = std::string{"xxxxxxxx"};
  int v = 1234;
  auto result = std::to_chars(str.data(), str.data() + str.size(), v);
  cout << "str: " << str << ", filled: " << result.ptr - str.data()
       << " characters." << endl;
  // str: 1234xxxx, filled: 4 characters.

  return 0;
}

2.2 std::variant

C++17中提供了std::variant类型,意为多变的,可变的类型,类似于加强版的union,里面可以存放复合数据类型,且操作元素更为方便。

int main() {
  std::variant<int, std::string> var("hello");
  cout << var.index() << endl;

  var = 123;
  cout << var.index() << endl;

  try {
    var = "world";
    std::string str = std::get<std::string>(var); // 通过类型获取
    var = 3;
    int i = std::get<0>(var); // 通过索引获取
    cout << str << ", " << i << endl;
  } catch (...) {
  }

  return 0;
}

2.3 std::optional

该类型主要用于简化函数返回值的判断,自己也没有深入去挖掘,附上一个简单例子:

std::optional<int> StoI(const std::string &str) {
  try {
    return std::stoi(str);
  } catch (...) {
    return std::nullopt;
  }
}

int main() {
  std::string str{"1234"};
  std::optional<int> result = StoI(str);
  if (result) {
    cout << *result << endl;
  } else {
    cout << "StoI() error." << endl;
  }

  return 0;
}

2.4 std::any

在C++11中引入的auto自动推导类型变量大大方便了编程,但是auto变量一旦声明,该变量类型不可再改变。C++17中引入了std::any类型,该类型变量可以存储任何类型的值,也可以时刻改变它的类型,类似于python中的变量。

int main() {
  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() << endl;
  }
  a.reset();
  if (a.has_value()) {
    cout << a.type().name();
  }
  a = std::string("hello");
  cout << a.type().name() << ", " << std::any_cast<std::string>(a) << endl;

  return 0;
}

2.5 std::apply

将tuple元组解包,并作为函数的传入参数,如下代码:

int add(int a, int b) { return a + b; }

int main() {
  auto add_lambda = [](auto a, auto b, auto c) { return a + b + c; };

  cout << std::apply(add, std::pair(2, 3)) << endl;
  cout << std::apply(add_lambda, std::tuple(2, 3, 4)) << endl;
  return 0;
}

2.6 std::make_from_tuple

解包tuple作为构造函数参数构造对象:

struct A {
  std::string _name;
  size_t _age;

  A(std::string name, size_t age) : _name(name), _age(age) { cout << "name: " << _name << ", age: " << _age << endl; }
};

int main() {
  auto param = std::make_tuple("kai", 18);
  std::make_from_tuple<A>(std::move(param));

  return 0;
}

2.7 std::string_view

C++中字符串有两种形式,char*std::string,string类型封装了char*字符串,让我们对字符串的操作方便了很多,但是会有些许性能的损失,而且由char*转为string类型,需要调用string类拷贝构造函数,也就是说需要重新申请一片内存,但如果只是对源字符串做只读操作,这样的构造行为显然是不必要的。在C++17中,增加了std::string_view类型,它通过char*字符串构造,但是并不会去申请内存重新创建一份该字符串对象,只是char*字符串的一个视图,优化了不必要的内存操作。相应地,对源字符串只有读权限,没有写权限。

void foo(std::string_view str_v) {
  cout << str_v << endl;
  return;
}

int main() {
  char *charStr = "hello world";
  std::string str{charStr};
  std::string_view str_v(charStr, strlen(charStr));

  cout << "str: " << str << endl;
  cout << "str_v: " << str_v << endl;
  foo(str_v);

  return 0;
}

2.8 std::as_const

将左值转化为const类型

int main() {
  std::string str{"hello world"};
  cout << std::is_const<decltype(str)>::value << endl;

  const std::string str_const = std::as_const(str);
  cout << std::is_const<decltype(str_const)>::value << endl;
  return 0;
}

2.9 std::filesystem

C++17中引入了filesystem,方便处理文件,提供接口函数很多,用起来也很方便,以下是一个简单的使用例子,详细可参考:Standard library header

int main() {
  fs::path str("./data");
  if (!fs::exists(str)) {
    cout << "path not exist." << endl;
    return -1;
  }
  fs::directory_entry entry(str);
  if (entry.status().type() == fs::file_type::directory) {
    cout << "it's a directory." << endl;
  }
  fs::directory_iterator list(str);
  for (auto &file : list) {
    cout << file.path().filename() << endl;
  }

  return 0;
}
// it's a directory.
// "text.md"
// "test.cpp"
// "test.txt"

2.10 std::shared_mutex

读写锁相关

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值