Last updated on 2024-07-29T22:23:06+08:00
                  
                  
                
              
            
            
              
                
                
    
构造函数模板推导 
 在 C++17 前构造一个模板类对象需要指明类型,C++17 就不需要特殊指定,直接可以推导出类型
1 2 3 4
   | pair<int, double> p(1, 2.2); 
  pair p(1, 2.2);  vector v = {1, 2, 3}; 
 
  | 
 
结构化绑定 
if-switch 语句初始化
1 2 3 4 5 6 7 8 9 10
   | 
  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++ 类的静态成员变量在头文件中是不能初始化的,但是有了内联变量,就可以达到此目的
1 2 3 4 5 6 7 8 9 10
   |  struct A {     static const int value;   }; inline int const A::value = 10;
 
  struct A {     inline static const int value = 10; }
 
  | 
 
折叠表达式
C++17 引入了折叠表达式使可变参数模板编程更方便
1 2 3 4 5 6 7 8
   | template <typename ... Ts> auto sum(Ts ... ts) {     return (ts + ...); } int a {sum(1, 2, 3, 4, 5)};  std::string a{"hello "}; std::string b{"world"}; cout << sum(a, b) << endl; 
 
  | 
 
constexpr lambda表达式 
C++17 前lambda 表达式只能在运行时使用,C++17 引入了 constexpr lambda 表达式,可以用于在编译期进行计算
1 2 3 4
   | int main() {      constexpr auto lamb = [] (int n) { return n * n; };     static_assert(lamb(3) == 9, "a"); }
 
  | 
 
constexpr函数有如下限制:函数体不能包含汇编语句、goto 语句、label、try 块、静态变量、线程局部存储、没有初始化的普通变量,不能动态分配内存,不能有 new delete 等,不能是虚函数
namespace嵌套
1 2 3 4 5 6 7 8 9 10 11 12
   | namespace A {     namespace B {         namespace C {             void func();         }     } }
 
  namespace A::B::C {     void func();) }
 
  | 
 
__has_include预处理表达式 
 可以判断是否有某个头文件,代码可能会在不同编译器下工作,不同编译器的可用头文件有可能不同,所以可以使用此来判断
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
   | #if defined __has_include #if __has_include(<charconv>) #define has_charconv 1 #include <charconv> #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          其它方式实现 #endif     return std::nullopt; }
 
  | 
 
在 lambda 表达式用 *this 捕获对象副本 
 正常情况下,lambda表达式中访问类的对象成员变量需要捕获 this,但是这里捕获的是this 指针,指向的是对象的引用,正常情况下可能没问题,但是如果多线程情况下,函数的作用域超过了对象的作用域,对象已经被析构了,还访问了成员变量,就会有问题
所以 C++17 增加了新特性,捕获 *this,不持有this 指针,而是持有对象的拷贝
1 2 3 4 5 6 7 8 9 10 11 12 13 14
   | 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 指示符,使用它们来给编译器提供一些额外的信息,来产生一些优化或特定的代码,也可以给其它开发者一些提示信息
1 2
   | 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,让编译期忽略警告
1 2 3 4 5 6 7 8 9 10
   | switch (i) {}     case 1:         xxx;      case 2:         xxx;          [[fallthrough]];           case 3:         xxx;        break; }
 
  | 
 
使得编译器和其它开发者都可以理解开发者的意图
 
[[nodiscard]]:表示修饰的内容不能被忽略,可用于修饰函数,标明返回值一定要被处理
1 2 3 4
   | [[nodiscard]] int func(); void F() {     func();  }
 
  | 
 
[[maybe_unused]]:提示编译器修饰的内容可能暂时没有使用,避免产生警告
1 2 3 4 5 6
   | void func1() {} [[maybe_unused]] void func2() {}  void func3() {     int x = 1;     [[maybe_unused]] int y = 2;  }
 
  | 
 
字符串转换 
  新增from_chars 函数和 to_chars 函数
  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
   | #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 }
 
  | 
 
std::variant
C++17 增加 std::variant 实现类似 union 的功能,但却比 union 更高级,举个例子 union 里面不能有 string 这种类型,但 std::variant 却可以,还可以支持更多复杂类型,如 map 等
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
   | 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 << endl;         cout << i << endl;     } catch(...) {              }     return 0; }
 
  | 
 
一般情况下 variant 的第一个类型一般要有对应的构造函数,否则编译失败
1 2 3 4 5 6
   | struct A {     A(int i){}   }; int main() {     std::variant<A, int> var;  }
 
  | 
 
可以使用 std::monostate 来打个桩,模拟一个空状态
1
   | std::variant<std::monostate, A> var; 
 
  | 
 
std::optional
有时候可能会有需求,让函数返回一个对象,如下:
1 2 3 4 5 6 7
   | struct A {}; A func() {     if (flag) return A();     else {              } }
 
  | 
 
有一种办法是返回对象指针,异常情况下就可以返回nullptr,但是这就涉及到了内存管理,也许会使用智能指针,但这里其实有更方便的办法就是std::optional
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
   | 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 可以存储任何类型的单个值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
   | 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();     }     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 展开作为函数的参数传入
1 2 3 4 5 6 7 8 9
   | 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";      std::cout << std::apply(add_lambda, std::tuple(2.0f, 3.0f)) << '\n'; }
 
  | 
 
std::make_from_tuple
使用 make_from_tuple 可以将 tuple 展开作为构造函数参数
1 2 3 4 5 6 7 8 9
   | 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 即可
1 2 3 4 5 6 7 8 9 10 11
   | 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 类型
1 2
   | std::string str = "str"; const std::string& constStr = std::as_const(str);
 
  | 
 
file_system
C++17 正式将 file_system 纳入标准中,提供了关于文件的大多数功能
1 2 3 4 5
   | 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,可以实现读写锁