- 1 Before C++17
- 2 Converting From Characters to Numbers: from_chars
- 2.1 示例
- 3 性能
- 4 C++23 更新
- 5 C++26 更新
- 6 C++ 中对 std::from_chars 的编译器支持
- 7 总结
本文为摘录,原文为: https://www.cppstories.com/2018/12/fromchars/
随着引入 C++17,C++标准库通过添加 std::from_chars
来扩展了将文本转换为数字的功能。这个低级、高性能的 API 相较
于以前的方法(如 atoi 和 stringstream)提供了显著的优势。在本文中,我们将探讨从 C++17 到 C++26 的字符串转换程序
的演变,重点突出了诸如 constexpr 支持和改进的错误处理等关键改进。让我们深入了解细节,看看 std::from_chars
如何
可以改变您对字符串转换的方法。
1 Before C++17
- sprintf / snprintf
- sscanf
- atol
- strtol
- strstream
- stringstream
- to_string
- stoi 等函数
而在 C++17 中,新增了一种选项: std::from_chars
!为什么需要新方法?难道旧方法不够好吗?
新的转换例程具有以下特点:
- 不抛出异常
- 不分配内存
- 不支持本地化
- 内存安全
- 错误报告提供有关转换结果的额外信息
虽然 API 可能不太友好,但很容易封装为外观模式。
一个简单的示例:
const std::string str { "12345678901234" };
int value = 0;
std::from_chars(str.data(), str.data() + str.size(), value);
// 省略了错误检查...
2 Converting From Characters to Numbers: from_chars
std::from_chars
,在 <charconv> 头文件中可用,是一组重载函数:用于整数类型和浮点类型。
对于整数类型,我们有以下函数:
std::from_chars_result from_chars(const char/ first,
const char/ last,
TYPE &value,
int base = 10);
其中,TYPE 展开为所有可用的有符号和无符号整数类型以及 char。
base 可以是从 2 到 36 的数字。
然后是浮点数版本:
std::from_chars_result from_chars(const char/ first,
const char/ last,
FLOAT_TYPE& value,
std::chars_format fmt = std::chars_format::general);
FLOAT_TYPE
展开为 float
、 double
或 long double
。
chars_format
是一个枚举,包含以下值:
- scientific、
- fixed、
- hex 和
- general(是 fixed 和 scientific 的组合)。
所有这些函数(对于整数和浮点数)的返回值是 from_chars_result
:
struct from_chars_result {
const char/ ptr;
std::errc ec;
};
from_chars_result
包含有关转换过程的信息。
以下是总结:
| 返回条件 || from_chars_result 的状态 | |——|—————————————————————————-| | 成功 || ptr 指向第一个不匹配模式的字符,或者如果所有字符匹配则为 last 的值,并且 ec 为值初始化状态。 | | 无效转换 || ptr 等于 first,ec 等于 std::errc::invalid_argument。value 未被修改。 | | 超出范围 || 数字太大,无法装入值类型。ec 等于 std::errc::result_out_of_range,ptr 指向第一个不匹配的字符。value 未被修改。 |
2.1 示例
以下是使用 from_chars
将字符串转换为数字的两个示例,一个是整数一个是浮点数。
2.1.1 整数类型
#include <charconv> // from_char, to_char
#include <string>
#include <iostream>
int main() {
const std::string str { "12345678901234" };
int value = 0;
const auto res = std::from_chars(str.data(),
str.data() + str.size(),
value);
if (res.ec == std::errc())
{
std::cout << "value: " << value
<< ", distance: " << res.ptr - str.data() << '\n';
}
else if (res.ec == std::errc::invalid_argument)
{
std::cout << "invalid argument!\n";
}
else if (res.ec == std::errc::result_out_of_range)
{
std::cout << "out of range! res.ptr distance: "
<< res.ptr - str.data() << '\n';
}
}
这个示例很简单,它将一个字符串 str 传递给 from_chars,然后在可能的情况下显示结果并提供附加信息。
你的任务是在编译器资源管理器中运行这段代码。
“12345678901234” 能够装入这个数字吗?或者你是否看到一些来自转换 API 的错误?
2.1.2 浮点数
这里是浮点数版本:
#include <charconv> // from_char, to_char
#include <string>
#include <iostream>
int main() {
const std::string str { "16.78" };
double value = 0;
const auto format = std::chars_format::general;
const auto res = std::from_chars(str.data(),
str.data() + str.size(),
value,
format);
if (res.ec == std::errc())
{
std::cout << "value: " << value
<< ", distance: " << res.ptr - str.data() << '\n';
}
else if (res.ec == std::errc::invalid_argument)
{
std::cout << "invalid argument!\n";
}
else if (res.ec == std::errc::result_out_of_range)
{
std::cout << "out of range! res.ptr distance: "
<< res.ptr - str.data() << '\n';
}
}
3 性能
我做了一些基准测试,新例程速度非常快!
一些数字:
- 在 GCC 上,它比 stoi 快大约 4.5 倍,比 atoi 快大约 2.2 倍,比 istringstream 快近 50 倍。
- 在 Clang 上,它比 stoi 快大约 3.5 倍,比 atoi 快 2.7 倍,比 istringstream 快 60 倍!
- MSVC 的性能比 stoi 快约 3 倍,比 atoi 快约 2 倍,比 istringstream 快近 50 倍。
你可以在我的 C++17 书籍中找到这些结果:《C++17 详细信息》。
4 C++23 更新
在 C++23 中,我们为我们的函数得到了一个改进:P2291
std::to_chars()
和 std::from_chars()
的整数重载现在是 constexpr
。
它已经在 GCC 13、Clang 16 和 MSVC 19.34 中实现。
与 std::optional 一起,它还可以在 constexpr 上下文中工作,我们可以创建以下示例:
#include <charconv>
#include <optional>
#include <string_view>
constexpr std::optional<int> to_int(std::string_view sv)
{
int value {};
const auto ret = std::from_chars(sv.begin(), sv.end(), value);
if (ret.ec == std::errc{})
return value;
return {};
};
int main() {
static_assert(to_int("hello") == std::nullopt);
static_assert(to_int("10") == 10);
}
在编译器资源管理器中运行
5 C++26 更新
这项工作尚未完成,在 C++26 中看起来我们将有更多的添加:
查看 P2497R0,这个提案已经被接受并包含在 C++26 的工作草案中:
对 <charconv> 函数成功或失败的测试
这一功能已在 GCC 14 和 Clang 18 中实现。
简而言之,from_chars_result(以及 to_chars_result)获得了一个 bool 转换运算符:
constexpr explicit operator bool() const noexcept;
它必须返回 ec =
std::errc{}= 。
这意味着我们的代码可能会更简单:
if (res.ec == std::errc()) { ... }
可以变成:
if (res) { ... }
例如:
// ...
const auto res = std::from_chars(str.data(),
str.data() + str.size(),
value,
format);
if (res)
{
std::cout << "value: " << value
<< ", distance: " << res.ptr - str.data() << '\n';
}
// ...
6 C++ 中对 std::from_chars 的编译器支持
- Visual Studio: 完全支持 std::from_chars 是在 Visual Studio 2019 版本 16.4 中引入的,浮点支持从 VS 2017 版本 15.7 开始。Visual Studio 2022 包含了 C++23 功能,如对整数重载的 constexpr 支持。
- GCC: 从 GCC 11.0 开始,std::from_chars 提供完整支持,包括整数和浮点转换。最新的 GCC 版本,比如 GCC 13,包含 constexpr 整数支持。
- Clang: Clang 7.0 引入了对整数转换的初始支持。Clang 16 及以上支持整数重载的 constexpr。
获取最准确和最新的信息,请查看 CppReference、编译器支持。
7 总结
如果你想将文本转换为数字,并且不需要任何额外的功能,如区域设置支持,那么 std::from_chars 可能是最好的选择。它提供 了很好的性能,而且更重要的是,你将获得关于转换过程的大量信息(例如扫描了多少个字符)。
这些例程在解析 JSON 文件、三维文本模型表示(如 OBJ 文件格式)等方面可能特别有用。
而且新函数甚至可以在编译时使用(截至 C++23),并且在 C++26 中具有更好的错误检查功能。