前言

从C++11开始,标志着c++的复兴和现代C++的开端。尽管有各种新语言层出不穷,但涉及性能和系统层面c++仍然是首选,地位无可撼动。新C++ 有一些新特性非常好用,本文对此进行了介绍,比如 auto、lambda、constexpr、tuple、智能指针等。

最近很火的鸿蒙系统框架代码,很多就是基于c++做的底层、运行时和应用框架封装。c++11后续又有11,14,17,20等众多新版本。哪个是主流?截至目前来说,c++17应该是新项目的首选。C++14在11的基础上查缺补漏,并未加入许多新特性,而C++17作为C++11后的第一个大版本,标志着现代C++逐渐走向成熟。

As a language, C++ has evolved a lot.

Of course this did not happen overnight. There was a time when C++ lacked dynamism. It was difficult to be fond of the language.

But things changed when the C++ standard committee decided to spin up the wheel.

Since 2011, C++ has emerged as a dynamic and ever-evolving language that a lot of people have been hoping for.

这里总结下C++17在业务代码中好用的几个特性。

c++17新特性有:

1、对auto表达式推导的规则进行了改变;

2、lambda表达式可以捕获“*this”;

3、新增inline变量,可以直接将全局变量定义在头文件中;

4、条件表达式中支持初始化语句;

5、枚举的直接列表初始化等等。

......

1.auto关键字

从c++11开始,auto关键字能够通过初始化器推导出变量的类型。在c++14中,auto关键字的能力进一步提升,能够通过return语句推导出函数的返回类型。 使用auto关键字能够提高编码效率,同时能够简化重构流程。但是,C++11中的auto推导,往往结果与预期的不同。

c++11 中为了支持统一初始化,引入了新的统一初始化语法,如下所示。

// c++11auto x3{ 1, 2 }; // std::initializer_list<int>auto x4 = { 3 }; // decltype(x4) is std::initializer_list<int>auto x5{ 3 };    // std::initializer_list<int>

这三种方式初始化的变量,最终类型推导的结果都是 std::initializer_list , 而不是我们认为的int。 这是因为 当用于auto声明变量的表达式是{}括起来的,推导的型别就会变成 std::initializer_list。

在C++17中,对auto表达式推导的规则进行了改变。

// c++17auto x3{ 1, 2 }; // error: not a single elementauto x4 = { 3 }; // decltype(x4) is std::initializer_list<int>auto x5{ 3 };    // decltype(x5) is int

对比发现 auto x5{3}, 会直接将变量推导成 x5, 而 x3{1, 2} 这种方式也会编译失败。auto推导的规则变得更加直观。

2. lambda表达式

lambda也是c++11中引入的,在C++11中,lambda表达式只能用捕获this,this是当前对象的一个只读的引用。 在C++17中,可以捕获*this, *this是当前对象的一个拷贝,捕获当前对象的拷贝,能够确保当前对象释放后, lambda表达式能安全的调用this中的变量和方法。

3. inline变量

Inline 变量, inline变量可以让变量有多于一次的定义。C++17之前,我们定义全局变量, 总需要将变量定义在cpp文件中,然后在通过extern关键字来告诉编译器 这个变量已经在其他地方定义过了。 inline变量出现后,我们可以直接将全局变量定义在头文件中,而不用担心出现redefine的错误信息。

有兴趣的朋友可以看看下面两篇文章:

《c++ inline variable 内联变量 c++17》
《GCC,Clang 在C模式,较低优化等级下,链接器对内联函数报未定义错误,为什么?》

4.namespace嵌套

namespace A {namespace B {namespace C {void func();}}
}// c++17
namespace A::B::C {void func();
}

5.from_chars函数和to_chars

具体可参考《C++标准库里自带的数值类型和字符串互相转换函数》

std::array<char, 3> str{"42"};
int result;
std::from_chars( str.data(), str.data()+str.size(),result );
std::cout << result << std::endl;// p是填充到str以后的最后一个迭代器
if(auto [p, ec] = std::to_chars(str.data(), str.data() + str.size(), 425);ec == std::errc()){if(p == str.end()){std::cout << "hello world\n";}std::cout << std::string_view(str.data(), p - str.data());
}
输出:
42
hello world
425
#include <iostream>
#include <charconv>
#include <array>
#include <system_error>int main() {std::cout << "hello test" << std::endl;//std::array<char, 10> str;std::array<char,10> str{ "42"};int result;std::from_chars(str.data(), str.data() + str.size(), result,16);std::cout << result << std::endl;if (auto[p, ec] = std::to_chars(str.data(), str.data() + str.size(), 4321999,16);ec == std::errc()){std::cout << "ok:"<< str.data() << static_cast<int>(ec) <<std::endl;std::cout << std::string(str.data());}else{std::cout << "error,ec=" << std::make_error_code(ec) <<std::endl;}return 0;}

其他好用的新特性

结构化绑定

c++17最便利的语法糖当属结构化绑定。结构化绑定是指将array、tuple或struct的成员绑定到一组变量*上的语法,最常用的场景是在遍历map/unordered_map时不用再声明一个中间变量了:

// pre c++17
for(const auto& kv: map){const auto& key = kv.first;const auto& value = kv.second;// ...
}// c++17
for(const auto& [key, value]: map){// ...
}

需要注意的是,结构化绑定的结果并不是变量,c++标准称之为名字/别名,这也导致它们不允许被lambda捕获,但是gcc并没有遵循c++标准,所以以下代码在gcc可以编译,clang则编译不过。

for(const auto& [key, value]: map){[&key, &value]{std::cout << key << ": " << value << std::endl;}();
}

在clang环境下,可以在lambda表达式捕获时显式引入一个引用变量通过编译:

for(const auto& [key, value]: map){[&key = key, &value = value]{std::cout << key << ": " << value << std::endl;}();
}

但是这条限制在c++20中已经被删除,所以在c++20标准中gcc和clang都可以捕获结构化绑定的对象了。上述第一种写法在c++20里都是ok的。

std::tuple的隐式推导

在c++17以前,构造std::pair/std::tuple时必须指定数据类型或使用std::make_pair/std::make_tuple函数,c++17为std::pair/std::tuple新增了推导规则,可以不再显示指定类型。

// pre c++17
std::pair<int, std::string> p1{3.14, "pi"s};
auto p1 = std::make_pair(3.14, "pi"s);// c++17
std::pair p3{3.14, "pi"s};

if constexpr

if constexpr语句是编译期的if判断语句,在C++17以前做编译期的条件判断往往通过复杂SFINAE机制或模版重载实现,甚至嫌麻烦的时候直接放到运行时用if判断,造成性能损耗,if constexpr大大缓解了这个问题。比如我想实现一个函数将不同类型的输入转化为字符串,在c++17之前需要写三个函数去实现,而c++17只需要一个函数。

// pre c++17
template <typename T>
std::string convert(T input){return std::to_string(input);
}// const char*和string进行特殊处理
std::string convert(const char* input){return input;
}
std::string convert(std::string input){return input;
}
// c++17
template <typename T>
std::string convert(T input) {if constexpr (std::is_same_v<T, const char*> ||std::is_same_v<T, std::string>) {return input;} else {return std::to_string(input);}
}

if初始化语句

c++17支持在if的判断语句之前增加一个初始化语句,将仅用于if语句内部的变量声明在if内,有助于提升代码的可读性。且对于lock/iterator等涉及并发/RAII的类型更容易保证程序的正确性。

if (auto it = m.find(10); it != m.end()) { return it->second.size(); }
if (char buf[10]; std::fgets(buf, 10, stdin)) { m[0] += buf; }
if (std::lock_guard lock(mx); shared_flag) { unsafe_ping(); shared_flag = false; }

std::shared_mutex

读写锁,性能提升

关于与mutex的性能对比可以看这里《std::shared_mutex和std::mutex的性能对比(benchmark)》

shared_mutex是c++的原生读写锁实现,有共享和独占两种锁模式,适用于并发高的读场景下,通过reader之前共享锁来提升性能。在c++17之前,只能自己通过独占锁和条件变量自己实现读写锁或使用c++14加入的性能较差的std::shared_timed_mutex。

通过shared_mutex实现的线程安全计数器:

// c++17
class ThreadSafeCounter {public:ThreadSafeCounter() = default;// Multiple threads/readers can read the counter's value at the same time.unsigned int get() const {std::shared_lock lock(mutex_);return value_;}// Only one thread/writer can increment/write the counter's value.unsigned int increment() {std::unique_lock lock(mutex_);return ++value_;}// Only one thread/writer can reset/write the counter's value.void reset() {std::unique_lock lock(mutex_);value_ = 0;}private:mutable std::shared_mutex mutex_;unsigned int value_ = 0;
};

std::string_view

std::string_view顾名思义是字符串的“视图”,类成员变量包含两个部分:字符串指针和字符串长度,std::string_view涵盖了std::string的所有只读接口。std::string_view对字符串不具有所有权,且兼容std::string和const char*两种类型。c++17之前,我们处理只读字符串往往使用const std::string&,std::string有两点性能优势:

  1. 兼容两种字符串类型,减少类型转换和内存分配。如果传入的是明文字符串const char*, const std::string&需要进行一次内存分配,将字符串拷贝到堆上,而std::string_view则可以避免。
  2. 在处理子串时,std::string::substr也需要进行拷贝和分配内存,而std::string_view::substr则不需要,在处理大文件解析时,性能优势非常明显。

平时代码中可以大规模使用的一个特性。其实对于string的争论一直没有停止过,很多人认为string是字节串而不是字符串,因为string是可以改变的,这一切争论到C++17可以停止了。string_view的substr与构造时间复杂度为O(1),且不会产生拷贝,因为substr只是一个指针操作。

可以参考如下两篇文章:
《C++17,使用 string_view 来避免复制》
《C++17 string_view的高效以及陷阱》

std::file_system

具体可参考《c++ filesystem》

其实模子是boost的file_system,最早2003年就出来了,因为是跨平台的,所以可以说是非常舒服了。

namespace fs = std::filesystem;
fs::path pathToShow("/home/lzl/Desktop/execise");
cout << "exists() = " << fs::exists(pathToShow) << "\n"
<< "root_name() = " << pathToShow.root_name() << "\n"
<< "root_path() = " << pathToShow.root_path() << "\n";

std::map/unordered_map try_emplace 

在向std::map/unordered_map中插入元素时,我们往往使用emplace,emplace的操作是如果元素key不存在,则插入该元素,否则不插入。但是在元素已存在时,emplace仍会构造一次待插入的元素,在判断不需要插入后,立即将该元素析构,因此进行了一次多余构造和析构操作。c++17加入了try_emplace,避免了这个问题。同时try_emplace在参数列表中将key和value分开,因此进行原地构造的语法比emplace更加简洁。

std::map<std::string, std::string> m;
// emplace的原地构造需要使用std::piecewise_construct,因为是直接插入std::pair<key, value>
m.emplace(std::piecewise_construct,std::forward_as_tuple("c"),std::forward_as_tuple(10, 'c'));// try_emplace可以直接原地构造,因为参数列表中key和value是分开的
m.try_emplace("c", 10, 'c')

同时,c++17还给std::map/unordered_map加入了insert_or_assign函数,可以更方便地实现插入或修改语义。

std::apply

相当于把容器的值当做函数的输入。

int add_ten(int first, int second) { return first + second; }
auto add_ten_lambda = [](auto first, auto second) { return first + second; };std::cout << std::apply(add_ten, std::pair(1, 2)) << '\n';
//std::cout << add(std::pair(1, 2)) << "\n"; // error
std::cout << std::apply(add_ten_lambda, std::tuple(2.2f, 3.0f)) << '\n';输出:
3
5.2

类型系统

c++17进一步完备了c++的类型系统,终于加入了众望所归的类型擦除容器(Type Erasure)和代数数据类型(Algebraic Data Type)。

std::any

std::any是一个可以存储任何可拷贝类型的容器,C语言中通常使用void*实现类似的功能,与void*相比,std::any具有两点优势:

  1. std::any更安全:在类型T被转换成void*时,T的类型信息就已经丢失了,在转换回具体类型时程序无法判断当前的void*的类型是否真的是T,容易带来安全隐患。而std::any会存储类型信息,std::any_cast是一个安全的类型转换。
  2. std::any管理了对象的生命周期,在std::any析构时,会将存储的对象析构,而void*则需要手动管理内存。

std::any应当很少是程序员的第一选择,在已知类型的情况下,std::optional, std::variant和继承都是比它更高效、更合理的选择。只有当对类型完全未知的情况下,才应当使用std::any,比如动态类型文本的解析或者业务逻辑的中间层信息传递。

具体可查看这篇文章《C++17之std::any》

std::optional

std::optional<T>代表一个可能存在的T值,对应Haskell中的Maybe和Rust/OCaml中的option,实际上是一种Sum Type。常用于可能失败的函数的返回值中,比如工厂函数。在C++17之前,往往使用T*作为返回值,如果为nullptr则代表函数失败,否则T*指向了真正的返回值。但是这种写法模糊了所有权,函数的调用方无法确定是否应该接管T*的内存管理,而且T*可能为空的假设,如果忘记检查则会有SegFault的风险。

具体可参考《C++干货系列——C++17新特性之std::optional》

// pre c++17
ReturnType* func(const std::string& in) {ReturnType* ret = new ReturnType;if (in.size() == 0)return nullptr;// ...return ret;
}// c++17 更安全和直观
std::optional<ReturnType> func(const string& in) {ReturnType ret;if (in.size() == 0)return nullopt;// ...return ret;
}

std::variant

std::variant<T, U, ...>代表一个多类型的容器,容器中的值是制定类型的一种,是通用的Sum Type,对应Rust的enum。是一种类型安全的union,所以也叫做tagged union。与union相比有两点优势:

  1. 可以存储复杂类型,而union只能直接存储基础的POD类型,对于如std::vector和std::string就等复杂类型则需要用户手动管理内存。
  2. 类型安全,variant存储了内部的类型信息,所以可以进行安全的类型转换,c++17之前往往通过union+enum来实现相同功能。

通过使用std::variant<T, Err>,用户可以实现类似Rust的std::result,即在函数执行成功时返回结果,在失败时返回错误信息,上文的例子则可以改成。

需要注意的是,c++17只提供了一个库级别的variant实现,没有对应的模式匹配(Pattern Matching)机制,而最接近的std::visit又缺少编译器的优化支持,所以在c++17中std::variant并不好用,跟Rust和函数式语言中出神入化的Sum Type还相去甚远,但是已经有许多围绕std::variant的提案被提交给c++委员会探讨,包括模式匹配,std::expected等等。

总结一下,c++17新增的三种类型给c++带来了更现代更安全的类型系统,它们对应的使用场景是:

  • std::any适用于之前使用void*作为通用类型的场景。
  • std::optional适用于之前使用nullptr代表失败状态的场景。
  • std::variant适用于之前使用union的场景。

并行算法库

这可以说是C++17最重要的几个特性之一,这个特性为几乎所有标准库函数加上一个执行策略参数,可以让使用者选择并行还是串行,这不仅包括七个新的算法,还有我们熟知的sort等。

具体可参考:
《[译]C++17,标准库新引入的并行算法 》
《STL并行算法库》

其他特性:

除此之外,C++17还增加了一些其他特性,文中没有一一列出。

  • bool 表达式不能用 ++, – 这两个自增(减)运算符了

  • c++17中异常已经成为了类型系统的一部分,

  • 枚举的直接列表初始化

  • 结构化绑定

  • constexpr if 表达式

  • map支持merge和extract

引用

c++17新特性有哪些-C#.Net教程-PHP中文网

C++17新特性_peanut_wu的博客-CSDN博客_c++17

C++17特性一览_李兆龙的博客的博客-CSDN博客_c++17

c++17新特性_每个开发者都应该了解的一些 C++ 特性_weixin_39527078的博客-CSDN博客

Some awesome modern C++ features that every developer should know

c++17好用的新特性总结相关推荐

  1. React16、17、18版本新特性

    react-16版本新特性 一.hooks import { useState } from 'react'function App() {// 参数:状态初始值比如,传入 0 表示该状态的初始值为 ...

  2. C++11、C++14、C++17、C++20新特性总结(5万字详解)

    文章目录 C++ 11是什么,C++ 11标准的由来 C++ auto类型推导完全攻略 auto 类型推导的语法和规则 auto 的高级用法 auto 的限制 auto 的应用 使用 auto 定义迭 ...

  3. 从 Java 12 到 Java 17 那些激动人心的新特性

    点击"终码一生",关注,置顶公众号 每日技术干货,第一时间送达! 2021 年 9 月,Oracle 发布了 Java 17,Java 的下一个长期支持版本.如果你在使用 Java ...

  4. JDK/Java 17 可能带来什么新特性?

    点击上方"朱小厮的博客",选择"设为星标" 后台回复"书",获取 后台回复"k8s",可领取k8s资料 JDK/Java ...

  5. JAVA 17版本介绍(二)新特性介绍

    文章目录 1.随着always-strict 浮点语义的恢复 2.增强型伪随机数生成器 3.新的 macOS 渲染管线 4.macOS/AArch64 端口 5.弃用 Applet API 6.JDK ...

  6. 你需要了解的 C++ 17 Top 19 新特性(附精彩评论)

    什么是 C++17? C++17(或 C++1z)是继 C++14 之后 C++ 编程语言 ISO/IEC 标准的下一次修订的非正式名称.C++17 现在功能已齐全,正在成为国际标准的路上.它的规范已 ...

  7. SpringBoot 3.0最低版本要求的JDK 17,这几个新特性不能不知道!

    △Hollis, 一个对Coding有着独特追求的人△ 这是Hollis的第 387 篇原创分享 作者 l Hollis 来源 l Hollis(ID:hollischuang) 最近,有很多人在传说 ...

  8. Go1.17 这个新特性竟是 6 年前提出来的

    作者 | 站长polaris       责编 | 欧阳姝黎 Go1.17 预计在 8 月份发布.目前 tip.golang.org 可以浏览 Go1.17 的相关内容,https://tip.gol ...

  9. 2022年最细Java 17新特性,是真的猛,被征服了!

    SpringBoot 正式支持Java 17,Kafka3.0弃用Java8 Spring Boot 2.5.5是Spring Boot 第一个支持Java 17的版本.现在你已经可以从Spring ...

  10. Java 17新特性,快到起飞?

    都说Java 8 是YYDS,那你注意到 Java 17 也是长期支持版本吗?目前按计划 JDK 19 将于今年 9 月发布 SpringBoot 正式支持Java 17,Kafka3.0弃用Java ...

最新文章

  1. 拿到WP官方主题Twenty Ten就是一顿nofollow伺候
  2. JVM---虚拟机栈(局部变量表)
  3. php tp5路由教程,附件八 TP5路由设置源码
  4. 手把手教你搭建Linux开发环境(VMware+Ubuntu)(四)——gcc编译器的安装和使用
  5. 【51单片机快速入门指南】4.5:I2C 与 TCA6416实现双向 IO 扩展
  6. npu算力如何计算_CPU、GPU、NPU、FPGA等芯片架构特点分析
  7. mysql统计今天发布了多少条_Mysql统计总结 - 最近30天,昨天的数据统计
  8. pdol链接mysql_MySQL5.7.11免安装版的安装和配置:解决MYSQL服务无法启动问题
  9. 经济师计算机考试取消,2019年经济师考试计算机机考答题要求及说明
  10. DeBruijin DFS
  11. 6Linux 终端命令格式
  12. MyEclipse安装插件的几种方法
  13. 路由器玩机---老毛子设置AP与主路由设置
  14. Found existing installation:xxxx
  15. 卷积神经网络的参数计算
  16. 学法减分拍照识题小程序开发
  17. 从零开始VCS+Verdi 安装过程
  18. hadoop之mapreduce教程+案例学习(二)
  19. 防复制防破解小区门禁梯控升级非联网CPU卡脱机写卡门禁梯控一卡通系统92HID623CPU V5.00操作说明之用户卡加密发卡设置说明
  20. 无盘服务器0x00000124,蓝屏0x00000124,懂的帮我一下

热门文章

  1. 互动拍照 — 子弹时间
  2. 史上最简单的LSTM文本分类实现:搜狗新闻文本分类(附代码)
  3. Godot简单的斜抛运动
  4. 计算机网络 华东理工大学 第7章测试
  5. R语言和医学统计学(4):秩和检验
  6. 使用Biopython访问NCBI's Entrez数据库
  7. html页面如何嵌套if标签,IF嵌套10个以上
  8. 个人主页网页设计模板
  9. 用友U8+数据导入 操作指引
  10. 自组织特征映射网络(SOFM)