C++での文字コード変換
C++で文字コードを変換したい場合は、、、
標準ライブラリに変換機能はないので、Cと同じように外部ライブラリを使いましょう。
標準ライブラリに一時期codecvtというものもありましたが、C++17で結局非推奨となりました。あきらめて外部ライブラリを使いましょう。
WindowsならAPI(MultiByteToWideChar()やWideCharToMultiByte())でできたはずですが、Linuxなら個人的にはiconvを使います。ただし、iconvの引数仕様は若干扱い辛いので、私は以下のようにヘルパー関数を用意しています。
文字コード変換処理 - encoding.h
#pragma once
#include <algorithm>
#include <cassert>
#include <cmath>
#include <cstdint>
#include <string>
#include <system_error>
#include <vector>
#include <errno.h>
#include <iconv.h>
namespace encoding {
using outbuffer = std::vector<char>;
// iconv RAII class
class iconv_wrapper {
private:
iconv_t iconv_;
public:
iconv_wrapper(const char* dst_encode, const char* src_encode) {
iconv_ = iconv_open(dst_encode, src_encode);
if (reinterpret_cast<std::intptr_t>(iconv_) == -1) {
throw std::runtime_error("iconv_open error");
}
}
iconv_wrapper(const iconv_wrapper& other) = delete;
iconv_wrapper& operator=(const iconv_wrapper& other) = delete;
iconv_wrapper(iconv_wrapper&& other) = delete;
iconv_wrapper& operator=(iconv_wrapper&& other) = delete;
size_t convert(char **inbuf, size_t *inbytesleft,
char **outbuf, size_t *outbytesleft) {
return iconv(iconv_, inbuf, inbytesleft, outbuf, outbytesleft);
}
~iconv_wrapper() {
iconv_close(iconv_);
}
};
// Container type requires the following.
// reserve() method
// resize() method
// [] operator
template<class Container>
Container convert_(const char* buff, size_t buff_len, const char* from, const char* to) {
iconv_wrapper ic(to, from);
char* src = const_cast<char*>(buff);
auto len = buff_len;
const std::size_t expand_size = 1000;
Container result;
// Reduce reallocation
// Assuming conversion from sjis,euc-jp to utf8.
std::size_t capacity = len * 1.5;
capacity = static_cast<std::size_t>(std::ceil(static_cast<double>(capacity) / expand_size)) * expand_size;
result.reserve(capacity);
while (true) {
// expand
result.resize(result.size() + expand_size);
char* dst = &result[result.size() - expand_size];
std::size_t dst_size = expand_size;
auto size = ic.convert(&src, &len, &dst, &dst_size);
int ret = static_cast<int>(size);
if (ret == -1 && errno != E2BIG) {
throw std::system_error(errno, std::generic_category());
}
result.resize(dst - &result[0]);
if (ret == -1 && errno == E2BIG) {
continue;
}
assert(ret >= 0 && len == 0);
break;
}
return result;
}
inline outbuffer convert(const char* buff, size_t buff_len, const char* from, const char* to) {
return convert_<outbuffer>(buff, buff_len, from, to);
}
// For encodings terminated by \0.
inline std::string convert_to_string(const char* buff, size_t buff_len, const char* from, const char* to) {
return convert_<std::string>(buff, buff_len, from, to);
}
}
このヘッダファイルが提供する変換関数は以下の二つです。
outbuffer convert(const char* buff, size_t buff_len, const char* from, const char* to) std::string convert_to_string(const char* buff, size_t buff_len, const char* from, const char* to)
buffには変換対象の文字列が入っているバッファの先頭アドレスを渡し、buff_lenにはバッファの長さを指定します。fromには変換元文字コード、toには変換先文字コードを指定します。
convert()の返り値のoutbufferはstd::vector<char>です。UTF16のような文字列中に\0を含むWideCharacterなエンコーディングも扱えるようにstd::stringではなくstd::vector<char>で返しています。
UTF8やSJISのような\0で終端するMultiByteエンコーディングに変換したい場合は、convert_to_string()を使えばstd::stringで直接受け取ることができます。std::stringに直接変換結果を出力するので、std::vectorからstd::stringへの無駄なコピーも行いません。
使用例
#include <cstring> #include <iostream> #include <string> #include "encoding/encoding.h" int main() { const char* sjis = "\x82\xb1\x82\xf1\x82\xc9\x82\xbf\x82\xcd"; // SJIS encoded string // SJIS -> UTF-8 buffer(std::vector<char>) auto buff = encoding::convert(sjis, std::strlen(sjis), "SJIS", "UTF-8"); std::cout << std::string(&buff[0], buff.size()) << std::endl; // SJIS -> UTF-8 string auto str = encoding::convert_to_string(sjis, std::strlen(sjis), "SJIS", "UTF-8"); std::cout << str << std::endl; return 0; }
実行例
# g++ -Wall -O2 test.cc # ./a.out こんにちは こんにちは
制限としてはbuffに渡すバイト列はEncodingとして完全なものを渡す必要があります。
iconvはディスクリプタ(iconv_t)が指すオブジェクト内にconversion stateを保持するので、ISO-2022-JP(いわゆるJIS)のようなステートフルなエンコードの文字列でも少しずつ変換していくことができます。encoding::convert()では呼び出すたびにiconvのディスクリプタを作成しているので、conversion stateは毎回リセットされます。また、EINVAL時(*1)にバッファのどこまで処理したかの情報も返していません。このため、streamからテキストを少しずつ読みながら変換していくようなことはできません。
[参考]
- man 3 iconv
(*1) SJISの1バイト目で終了している場合等、不完全なシーケンスを見つけた時にEINVALを返します。streamからデータを読み込みながら変換していく場合は、データがどこで切れるかわからないのでEINVALを考慮して処理する必要があります。
投稿日:2021/12/17 02:15