内部表現はワイド文字

内部表現をワイド文字で、入出力をマルチバイト文字で行うための関数を定義してみた。長いけどほとんどコメントなので勘弁。

#include <cstdlib>
#include <cassert>
#include <string>
#include <vector>
#include <iostream>
#include <locale>
#include <stdexcept>

#ifdef _MSC_VER
#define NS_STD
#else
#define NS_STD std
#endif

namespace
{
#ifdef unix
  const unsigned WIDE_CHAR_SIZE = 4;
#else
  const unsigned WIDE_CHAR_SIZE = 2;
#endif

  const char * ERROR_MESSAGE = "conversion failed : "
                               "encountering invalid multibyte character.";
}

// str_to_wstr ---------------------------------------------------------------
// std::wstringへのデータ格納時に使う
std::wstring str_to_wstr(const std::string & src)
{
  // 空データ渡さないでください
  assert(src.empty() != true);

  // 文字列の長さを取得(1は終端のNULL文字の分)
  NS_STD::size_t mblength = src.size() + 1;

  // ワイド文字列への変換結果を受け取るvector<wchar_t>オブジェクトを生成
  std::vector<wchar_t> vwc(mblength);

  // ワイド文字列に変換してvector<wchar_t>に格納
  NS_STD::size_t wcresult = NS_STD::mbstowcs(&(vwc[0]), src.c_str(), mblength);

  if(wcresult != -1)
  {
    // 変換成功
    // std::wstringのオブジェクトを生成、
    // 変換した分で初期化したオブジェクトを返す
    return std::wstring(vwc.begin(), vwc.begin() + wcresult);
  }
  else
  {
    // 変換失敗
    // 原因は不正なマルチバイト文字に遭遇したものとわかっているので、
    // その旨の例外を投げる
    throw std::invalid_argument(ERROR_MESSAGE);
  }
}

// wstr_to_str ---------------------------------------------------------------
// std::stringを噛ませて出力させたりする
std::string wstr_to_str(const std::wstring & src)
{
  // 空データ渡さないでください
  assert(src.empty() != true);

  // マルチバイト文字列で必要とされるサイズを計算
  NS_STD::size_t wclength = src.size() * WIDE_CHAR_SIZE + 1;

  // 出力用バッファをvector<char>で確保
  std::vector<char> vmb(wclength);

  // マルチバイト文字列に変換
  NS_STD::size_t mbresult = NS_STD::wcstombs(&(vmb[0]), src.c_str(), wclength);

  if(mbresult != -1)
  {
    // 変換成功
    // std::stringのオブジェクトを生成、
    // 変換した分で初期化したオブジェクトを返す
    return std::string(vmb.begin(), vmb.begin() + mbresult);
  }
  else
  {
    // 変換失敗
    // 原因は不正なワイド文字に遭遇したものとわかっているので、
    // その旨の例外を投げる
    throw std::invalid_argument(ERROR_MESSAGE);
  }
}

と定義しておいて、

int main(int argc, char *argv[])
{
  // 実行マシンに設定されているロケールを使いますよ
  std::locale::global(std::locale(""));

  if(argc < 2)
  {
    std::cerr << "specify argument of some kind." << std::endl;
    return 1;
  }

  // std::invalid_argument例外を投げられる可能性があるのでtry-catch
  try
  {
    // マルチバイト文字列をワイド文字列へ変換して保持
    std::wstring wtemp = str_to_wstr(argv[1]);

    std::cout << sizeof(wtemp[0]) << std::endl;
    // ワイド文字列をマルチバイト文字列へ変換して表示
    std::cout << wstr_to_str(wtemp) << std::endl;
  }
  catch(std::exception &ex)
  {
    std::cerr << ex.what() << std::endl;
  }

  return 0;
}

って使う感じ。入出力にこいつらを噛ませるstd::wstringのラッパクラスなんかは汎用性が出るかも。一応STLとかC/C++の標準関数で済ませてるのでどこでも動くハズだけれども、UNIX/LINUX処理系やgccを持ってないので実際のところどうなのかわからん(笑*1。あとBoost使うならstd::vectorの代わりにboost::scoped_arrayでバッファを確保する形になるね。

問題点は…、

  • mblength, wclengthは文字数と一致することがまずない
  • のでstd::vectorでバッファを確保するときに明らかに多いサイズになる
  • 戻り値は値渡しなので空間的・時間的コストともに高め
  • 名前が紛らわしいのでもうちょっと考えないといけない

ってところかな。正直マルチバイト文字列の正しいサイズの求め方ってわからん。あったとしてもけっこうコストかかるんじゃないかなぁ。

*1:VC++6.0 with STLportとbcc32 5.5.1でコンパイル&実行確認。VC 2005 Express Editionだとstd::localeの扱いが異なるらしくこのままでは日本語表示できない(コンパイルはできた)。