归纳总结一些常见的(我见过的)或比较好理解的(我能理解的)C++17的特性,具体的全部特性参考链接C++17 - cppreference.com。
New language features
Variables
结构化绑定
首先先举一个例子,在leetcode刷题看题解经常会遇到这种写法。
1 | unordered_map<int, int> mp; |
其中的 auto [k, v]
就是结构化绑定,通过结构化绑定,我们可以很方便地获取 map
对应的值。
除去 map
之外,还可以应用于 pair
,tuple
,结构体和数组,如果加上引用,还可以修改对象的值。
还有就是,可以实现自定义类的结构化绑定以及结构化绑定不能应用于constexpr(据说c++20可以),我不懂就简单提一下。
if-switch 语句初始化
举个例子,c++17前,
1 | int a = getValue(); |
c++17后。
1 | if (int a = getValue();a > 10) { |
内联变量
在头文件里定义一个内联的变量或者对象,如果这个定义被多个编译单元使用,那么他们都指向同一个唯一的对象。
1 | //header file |
引入的原因,首先是c++中,不允许在类中初始化非const静态成员;
因为静态成员是属于类,而不属于某个对象,如果在类中初始化,会导致每个对象都包含该静态成员。
其次是如果在被多个cpp引用的头文件中定义类结构之外的变量会导致这些cpp中都定义了这个变量,导致重定义变量。
关于内联就可以再写一篇文章,这就是学习c++让我感到痛苦的地方,平常你轻视的地方,却蕴含着三言两语难以说清楚的门道。
Templates
类模版参数推导(CATD/class template argument deduction)
1 | //before c++17 |
constexpr
constexpr lambda表达式
C++17允许lambda函数成为constexpr,如果它们满足条件,就可以在需要编译时评估的上下文中使用
1 | constexpr auto lambda = [](int x) { return x * 2; }; |
编译期if(compile-time if constexpr)
if constexpr
是编译期的判断语句
1 | //不同输入类型转换为字符串 |
Namespaces
简化的命名空间嵌套(simplified nested namespaces)
1 | namespace A { |
__has_include预处理表达式
用来判断是否有某个头文件,代码可能在不同编译器下工作,不同编译器的可用头文件有可能不同。
新增Attribute
首先 Attribute
是一个关键字,用于指定一个函数、变量、类、模板或类型 trait 应该具有的特殊行为。
[[fallthrough]]
通常在使用 switch 语句时,如果case 后没有加break,编译器就会发出警告信息,在case处理部分添加这个属性,用于消除这个警告信息,表示这部分逻辑本意如此。此外该属性只能用在 switch 语句中。
1 | switch (i) {} |
[[nodiscard]]
被此属性修饰的函数,其返回值不应该被丢弃,如果被丢弃编译器就会发出警告信息;如果是修饰的枚举或者类,那么在对应函数返回该类型的时候也不应该丢弃结果。
1 | [[nodiscard]] int func(); |
[[maybe_unused]]
通常如果声明了一个变量但是从来没有使用过,编译器就会发出警告信息,使用该属性之后,编译器就会认为是故意为之,从而不再发出警告。需要注意,这个声明不会影响编译器的优化逻辑,在编译优化阶段,无用的变量还是会被处理掉。
lambda通过*this捕获对象副本(lambda capture of *this)
正常情况下,lambda捕获了this指针,如果this指向的对象析构了,而函数再被调用且访问了成员变量,就会有问题,结果往往是崩溃。
这个新特性让你捕获*this,持有了对象的拷贝,从而避免了上述问题。
1 | struct A { |
New library features
Utility types
- std::any适用于之前使用void*作为通用类型的场景。
- std::optional适用于之前使用nullptr代表失败状态的场景。
- std::variant适用于之前使用union的场景。
std::any
可以存储任意类型的单个值
1 | int main() { |
std::optional
见名知意,表示一个值可能存在,没有值就是默认的 std::nullopt
1 | std::optional<int> divide(int a, int b) { |
对 std::optional
对象使用 has_valut()
来判断是否有值,使用 *
或者value()
来取值
std::variant
类型安全的联合体(可以称之为变化体),功能上与union类似,但是更加高级。
- 类型安全,由于存储了内部的类型信息,所有可以进行安全的类型转换
- 可以存储复杂类型,union只能存储POD类型(Plain Old Date)
Memory management
共享指针支持动态数组(array support for std::shared_ptr)
1 | std::shared_ptr<uint8_t[]> sp(new uint8_t[extraDataLength_], [](uint8_t* ptr) { delete[] ptr; }); |
Compile-time programming(to-do)
Algorithms
并行算法
c++17支持STL并行执行,简单提一下,以std::sort为例
1 | std::sort(exe_policy, begin, end, comp); |
可以添加如下三种的执行策略
std::execution::seq
(顺序执行)std::execution::par
(并行执行)std::execution::par_unseq
(并行和向量化执行)
Iterators and containers
std::map/unordered_map try_emplace
向std::map/unordered_map
中插入元素往往使用emplace
,其操作是如果元素不存在就会插入元素,否则不插入,但是如果元素已经存在,此时仍会构造一次待插入的元素,在判断不需要插入后将该元素立刻析构,所以产生了额外的开销, try_emplace
就避免了这种问题。
Others
std::string_view
通常我们传递一个string时会触发对象的拷贝操作,大字符串的拷贝赋值操作会触发堆内存分配,很影响运行效率,有了string_view就可以避免拷贝操作,平时传递过程中传递string_view即可。
1 | void func(std::string_view stv) { cout << stv << endl; } |