T.H/ 2024年 7月 31日/ 技術

はじめに

こんにちは。T.H.です。
今回は、久々にC++に触れる機会がありまして、やはりC++の文字列は分かりにくい、というお話です。
また離れるとすぐ忘れそうなため簡単にまとめることにしました。
逆に普段からC++を使いこなしている方にとっては今更、という内容かもしれません。

  • 細かいところで正確性を犠牲にしている面があります。
  • C言語の文字列についての基本的な知識がある前提とします。

マルチバイト文字列

  • std::string (char型)

こちらは古典的C言語由来の文字列と考えてもらえれば大体大丈夫という認識です。
文字コードがcharの配列として順番に並んでおり、終端として\0が入っています。
Cと同じように添え字でアクセスした場合は1バイト取得できます。
Cと同じように中身がasciiであることが分かっていればコード指定で操作する事もできます。

std::string str = 'abc あいうえお';

ワイド文字列

  • std::wstring (wchar_t型)

こちらはUnicode格納用の文字列と考えれば大体大丈夫です。(厳密には異なります)
添え字でアクセスした場合は1バイトではなく1文字取得できます。
必要に迫られない限り文字列がどのようなバイト列で格納されているかは考えないようにしましょう。
終端は同じく\0のようです。
文字リテラルの場合はプレフィックス"L"が必要になります。

std::wstring wstr = L'abc あいうえお';

相互変換

なんといまだに一発で変換できる標準関数がありません。
codecvtというライブラリがC++11で実装されたのですが、セキュリティ上の欠陥等によりC++17で早々に非推奨になりました。C++26で削除になるそうですので使用しない方が良いでしょう。

軽く調べた範囲では標準ライブラリではmbstowcs/wcstombsやiconv、
標準外ではICUなどのライブラリを使うことが多いようです。デファクトスタンダードは何なんでしょうね?

Windows上のC++での取り扱い

さらに初見の分かりにくさに拍車を書けるのがWindows環境独自の定義群です。
下記が良く使用されているかと思います。

  • LPCTSTR
  • TCHAR
  • tstring
  • _Tマクロ: _T("文字列")のような形式
  • その他、_t付の文字列操作関数 (_tcscpyなど)

いずれも、マルチバイト文字列とワイド文字列の違いを吸収するために用意されています。

Visual Studioのプロジェクトの構成プロパティから文字セットを変更するとプロジェクトで使用する文字セットが変更されます。その際に両方のバージョンのコードを用意せずとも一つのコードでこなせるように両対応されたものとなります。
文字セットがUNICODEの場合はwchar_tベース、マルチバイトの場合はcharベースになるように定義されています。

tstring

先述の中でtstringはWindows標準で用意されたものではありません。
ですが一般的なテクニックとして広く使われています。

下記のように定義することが多いです。

typedef std::basic_string<TCHAR> tstring;

これはどういうことかといいますと、おおむね下記と同じような意図です。

#ifdef UNICODE # 文字セットをUNICODEに設定
typedef std::wstring tstring;
#else
typedef std::string tstring;
#endif

Windowsでのマルチバイト文字列とワイド文字列相互変換

Windows上のC++では相互変換の関数が用意されています。
MultiByteToWideCharとWideCharToMultiByteです。
それぞれを使用したサンプルを記載します。

#include <string>
#include <windows.h> 

std::wstring StringToWString(const std::string& str)
{
    // 変換に必要なサイズを取得
    int size_needed = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), static_cast<int>(str.size()), NULL, 0);
    // 変換先のwchar_t配列を作成
    std::wstring wstr(size_needed, 0);
    // 変換を実行
    MultiByteToWideChar(CP_UTF8, 0, str.c_str(), static_cast<int>(str.size()), &wstr[0], size_needed);
    return wstr;
}

std::string WStringToString(const std::wstring& wstr)
{
    // 変換に必要なサイズを取得
    int size_needed = WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), static_cast<int>(wstr.size()), NULL, 0, NULL, NULL);
    // 変換先のchar配列を作成
    std::string str(size_needed, 0);
    // 変換を実行
    WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), static_cast<int>(wstr.size()), &str[0], size_needed, NULL, NULL);
    return str;
}

最後に

C++は歴史が長く、役割が広いが故の複雑さだとは思いますが、それにしても文字列の型だけでこれだけパターンがあるのは大変です。
今回は触れていませんが、char16_t、char32_t等もあり、正直なところ全部を正確に把握しきれていません。
私の少ないC++の経験とわずかな調査だけでこれだけ出てくるので、まだ把握できていない内容もあると思います。
大概の言語はUnicodeが基本となって入出力の際に意識する程度で済んでいるのは有難い限りですね。

参考

About T.H

North Torch株式会社 プログラマ 技術的な経歴は.NETアプリケーションが一番長い。 その他はまだまだ勉強中。