C++新特性的一些经典使用-类型萃取

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(...))

逗号表达式会 先执行左侧表达式,再返回右侧表达式的值
因此这里的执行顺序是:

  1. 获取目录中的所有文件
  2. 判断是否存在 .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

用于判断类型是否为枚举类型:

1
std::is_enum<T>::value

如果 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;
}

输出:

1
1

这种方式在 日志系统、调试输出、状态码打印 等场景非常实用。


总结

现代 C++ 的许多语言特性可以显著提升代码的表达能力:

  • if 初始化语句(C++17):缩小变量作用域
  • Lambda 表达式:简化回调与条件逻辑
  • STL 算法(如 std::any_of:提高代码语义化程度
  • 类型萃取(type traits):在编译期进行类型判断
  • SFINAE / enable_if:实现更安全的模板重载

C++新特性的一些经典使用-类型萃取
https://weihehe.top/2025/12/31/C-新特性的一些经典使用/
作者
weihehe
发布于
2025年12月31日
许可协议