C++17 新的數字、字串轉換函式庫:std::from_chars

在 C++ 裡面,其實已經有不少在字串與數字間轉換的方法了,像是 sprintf、sscanf、atol、strtol、stringstream、to_string 等等…

而在 Boost C++ Libraries 裡面,為了各種原因,也有另外提供 lexical_castConvert 等函式庫,讓使用者可以更方便地進行文字和數字間的轉換。

C++17 裡面,又有另一個標準的選擇了!那就是 std::from_chars()參考)。

為什麼在已經有這麼多方法的情況下,還要再多弄一個出來呢?基本上,std::from_chars() 是一個相對低接、可以提供最好的效能的函式;或許使用上沒那麼便利,但是適用於非常在乎效能的時候。

為了效能,在設計上:

  • 他不會丟出例外
  • 有記憶體空間的配置
  • 也不支援區域設定(locale)
  • 會回報轉換的錯誤

他新的 header file 是 <charconv> 這個新的檔案,裡面只有 from_chars()to_chars() 這兩個函式。


from_chars() 來說,針對整數型別,他的介面是:

std::from_chars_result from_chars(
	const char* first, const char* last,
	TYPE  &value,
         int base = 10);

其中,字串是把開始的位置(first)和結束的位置(last),作為參數傳入。
輸出則是以參考的形式傳入(value),讓函式修改他的值。

最後,則是可以指定要轉換成幾進位,數字可以從 2 – 36(應該是考慮到英文字母的數量)。

而除了整數型別的版本外,還有浮點數的版本,這部分支援 float、double、long double 這三種型別,其介面如下:

std::from_chars_result from_chars(
	const char* first, const char* last,
	float& value,
	std::chars_format fmt = std::chars_format::general);

前面的參數都一樣,只是最後一個參數,變成是 std::chars_format,用來指定輸入字串的格式;除了一般形式的 std::chars_format::fixed 外,他也支援科學符號的 std::chars_format::scientific;而如果兩者都有可能的話,也可以使用預設的 std::chars_format::general

另外,他也支援十六進位的 std::chars_format::hex

它的使用狀況,大致上會是:

#include <iostream>
#include <charconv>
#include <array>
int main()
{
	std::array<char, 10> str{ "42" };
	int result;
	std::from_chars(str.data(), str.data() + str.size(), result);
	std::cout << result;
}

str 也可以改用 std::string 等格式來做處理。

另外,這個函式也會回傳一個 std::from_cahrs_result,讓使用者可以確認轉換的結果是否有問題。(可能的錯誤有 std::errc::invalid_argumentstd::errc::result_out_of_range


而如果要反向,把數字轉換成字串的話,則是可以使用新的 to_chars() 這個函式(參考)。

不過由於他不負責記憶體的配置,所以在輸出的字串的部分,必須要先自行配置好記憶體空間;而他輸出的字串也不是 NULL-terminated 的格式,所以如果是要拿來當作一般的字串用,會需要自己加上 NULL,或轉換成其他的格式。

在整數型別的部分,他的介面是:

std::to_chars_result to_chars(
	char* first, char* last,
	TYPE  &value,
         int base = 10);

參數的部分基本上和 from_chars() 是類似的,只是前面的輸入、輸出是反過來的。

而浮點數的部分,介面則是:

std::to_chars_result to_chars(
	char* first, char* last, float value,
	std::chars_format fmt,
	int precision);

格式上,一樣是有 float、double、long double 三種版本。

其中,最後兩個參數、格式(fmt)和精度(precision)都是可以省略的。

下面就是一個範例:

#include <iostream>
#include <charconv>
#include <system_error>
#include <string_view>
#include <array>
int main()
{
  std::array<char, 10> str;
  if (auto[p, ec] = std::to_chars(str.data(), str.data() + str.size(), 42);
      ec == std::errc())
  std::cout << std::string_view(str.data(), p - str.data());
}

這邊可以看到,他是用一個 std::array<> 來儲存輸出的結果;而為了可以透過 cout 輸出,這邊是把她轉換成 std::string_view參考)來輸出。

比較特別的,是這邊為了要知道他到底輸出到哪裡,所以一定要接他回傳的 to_chars_result;在它裡面,除了 std::errc 外,還有一個 char* 是用來記錄最後的位置,所以上面的 p – str.data() 基本上就是字串的長度了。

而如果要轉成一般的字串,也可以寫成

std::string s(str.data(), p);

這樣的形式。

而如果給的字串長度不夠的話,他也會回傳 std::errc::value_too_large 來讓使用者知道。


std::from_chars()to_chars() 的簡單使用大概就是這樣了。

基本上,這兩個函式因為要自己去計算字串的大小、配置記憶體空間,在使用上其實不算很方便,以把數字轉字串來說,可以直接輸出 std::stringto_string() 其實更為方便。

而如果要講格式化輸出的話,Boost 的 lexical_castConvert,都有更多的功能和彈性。

不過,這兩種新的函式,實際上最大的重點還是他們的效能。根據《How to Use The Newest C++ String Conversion Routines – std::from_chars》的說法:

  • 在 gcc 中,他的效能是 stoi 的 4.5 倍、atoi 的 2.2 倍、istringsteam 的 50 倍
  • 在 clang 中,他的效能是 stoi 的 3.5 倍、atoi 的 2.7 倍、istringsteam 的 60 倍
  • 在 MSVC 中,他的效能是 stoi 的 3 倍、atoi 的 2 倍、istringsteam 的 50 倍

可以看到,效能的差異其實不算小。所以如果是要頻繁、大量地轉換的話,多花一些功夫,改用這系列的函式來做,應該是可以減少不少分析時間的~

所以,以個人來說,這兩組新的函式,應該算是比較適合寫在更底層的東西了。


附註:目前有完整支援的編譯器似乎還只有 Visual Studio 2017 15.9,GCC 8.0 和 CLang 7.0 都還只有整數、不支援其他型別。

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。