1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| # C++ 新特性的一些经典使用
在现代 C++(C++11 及之后版本)中,语言引入了大量提升表达能力和代码可读性的特性,例如 **初始化语句、lambda 表达式、类型萃取(type traits)以及 SFINAE** 等。这些特性不仅能减少样板代码,还能使逻辑表达更加清晰。本文结合两个实际开发中的例子,展示这些特性的典型用法。
---
## 保留内部包含 `.png` 文件的子目录
在文件处理或数据预处理程序中,经常需要 **筛选符合条件的目录**。例如:只保留那些内部包含 `.png` 文件的子目录。
下面的代码实现了这一逻辑:
```cpp auto input_metadata_dir_local8 = getSubDir( toStdString(configure.get_first_directory()), true);
if (input_metadata_dir_local8.empty()) { u8log::error() << u8"输入路径中没有数据..." << u8log::endl; return 1; }
for (auto it = input_metadata_dir_local8.begin(); it != input_metadata_dir_local8.end(); ) { try { std::cout << *it;
if (std::vector<std::string> files; (files = getAllFiles(*it, false), std::any_of(files.begin(), files.end(), [](const std::string& file) { return getFileSuffix(file) == "png"; }))) { ++it; } else { it = input_metadata_dir_local8.erase(it); } } catch (const std::exception& ex) { std::cout << ex.what() << std::endl; ++it; } }
|
关键特性解析
这段代码中包含几个非常典型的现代 C++ 写法。
1. if 语句中的初始化(C++17)
1
| if (std::vector<std::string> files; condition)
|
这是 C++17 引入的语法,它允许在 if 语句中定义一个临时变量,其作用域仅限于 if 语句块。
传统写法通常是:
1 2 3
| std::vector<std::string> files; files = getAllFiles(path, false); if (...)
|
使用初始化语句可以让变量的生命周期更加清晰,避免污染外部作用域。
2. 逗号表达式
代码中使用了逗号表达式:
1 2
| (files = getAllFiles(*it,false), std::any_of(...))
|
逗号表达式会 先执行左侧表达式,再返回右侧表达式的值。
因此这里的执行顺序是:
- 获取目录中的所有文件
- 判断是否存在
.png 文件
3. std::any_of + Lambda
1 2 3 4 5
| std::any_of(files.begin(), files.end(), [](const std::string& file) { return getFileSuffix(file) == "png"; });
|
std::any_of 是 <algorithm> 中的算法,用于判断 容器中是否存在满足条件的元素。
Lambda 表达式则提供了一个简洁的匿名函数,用于描述判断条件。
如果使用传统写法,代码通常是:
1 2 3 4 5 6 7 8 9
| bool found = false; for (const auto& file : files) { if (getFileSuffix(file) == "png") { found = true; break; } }
|
显然现代写法更加简洁和语义化。
4. 迭代器安全删除
1
| it = input_metadata_dir_local8.erase(it);
|
在遍历容器时删除元素需要格外小心。
erase 返回 下一个有效迭代器,因此可以安全继续遍历。
这是 STL 容器中一个非常重要的惯用写法。
判断是否为枚举并打印枚举值
在日志系统或调试输出中,我们经常希望直接打印枚举值。但 enum 类型默认不能直接通过 operator<< 输出为数值。
可以利用 类型萃取(type traits)和 SFINAE,为所有枚举类型统一实现一个输出运算符。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| #include <iostream> #include <type_traits>
template<typename T> std::ostream& operator<<( typename std::enable_if< std::is_enum<T>::value, std::ostream >::type& stream, const T& e) { return stream << static_cast< typename std::underlying_type<T>::type>(e); }
|
原理解析
这段代码使用了三个重要的 C++ 模板工具。
1. std::is_enum
用于判断类型是否为枚举类型:
如果 T 是枚举类型,则为 true。
2. std::enable_if(SFINAE)
enable_if 用于在编译期控制模板是否参与重载。
1
| typename std::enable_if<std::is_enum<T>::value, std::ostream>::type
|
含义是:
只有当 T 是枚举类型时,这个 operator<< 才会被启用。
如果不是枚举类型,该模板会被编译器忽略,从而避免污染普通类型的输出运算符。
3. std::underlying_type
枚举在底层其实是整数类型,例如:
1 2 3 4 5 6
| enum Color { Red = 1, Green = 2, Blue = 3 };
|
其底层类型可能是 int。
std::underlying_type 可以获取这个类型:
1
| std::underlying_type<Color>::type
|
因此代码通过 static_cast 将枚举转换为其底层整数类型再输出。
使用示例
1 2 3 4 5 6 7 8 9 10 11
| enum class Status { OK = 0, Error = 1 };
int main() { Status s = Status::Error; std::cout << s << std::endl; }
|
输出:
这种方式在 日志系统、调试输出、状态码打印 等场景非常实用。
总结
现代 C++ 的许多语言特性可以显著提升代码的表达能力:
- if 初始化语句(C++17):缩小变量作用域
- Lambda 表达式:简化回调与条件逻辑
- STL 算法(如
std::any_of):提高代码语义化程度
- 类型萃取(type traits):在编译期进行类型判断
- SFINAE /
enable_if:实现更安全的模板重载