対象読者
- macOSやLinuxの作法 (BOMなしUTF-8・char型もUTF-8・Makefileやそれを出力できるビルドシステム使用) でC++を書いている・書きたい人
- Cygwin・MinGW・MSYS・WSLのGCCでなく、MSVCでC++プログラムをコンパイルしたい人
- C++でクロスプラットフォーム開発したい人
Windows(MSVC)でもそれ以外のOSでもビルドできるようにする
**特段の理由がなければCMakeを使いましょう。**最近はVisual Studioの対応も良好です。
ソースコードでUTF-8を使う場合、以下の2行を加えましょう。
add_compile_options("$<$<C_COMPILER_ID:MSVC>:/utf-8>")
add_compile_options("$<$<CXX_COMPILER_ID:MSVC>:/utf-8>")
この/utf-8
オプションは、コードのプリプロセッシング・コンパイルを行う際に、入力をUTF-8とみなすという意味です。ないと殆どの日本語環境でShift-JISとみなされます。また、.editorconfig
というファイルをルートに作成し、適切に設定する文字コード・改行コード・インデントスタイル等をエディタによらず統一できます。例を以下に示します。
root = true
[*]
charset = utf-8
end_of_line = lf
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
tab_width = 8
[*.{py,rs}]
indent_size = 4
[{Makefile,*.{go,mk}}]
indent_style = tab
[*.md]
trim_trailing_whitespace = false
[*.{manifest,vcxproj,sln}]
charset = utf-8-bom
CMakeLists.txt
には、
add_compile_options("$<$<CXX_COMPILER_ID:MSVC>:/Zc:__cplusplus>")
の行も加えるとよいです。これは、C++11以降を利用した場合にマクロ変数__cplusplus
の値を利用中のC++のバージョンに合わせるという意味合いです。つけないと、C++98相当の値となります。(一応MSVC独自のマクロ変数_MSVC_LANG
で有効化しているC++のバージョンの判定は可能です、__cplusplus
での判定と併用しましょう)
さらに、VS2019のMSVCはデフォルトでC++14なので、次の行も足してC++17に対応させるとよいでしょう。
set(CMAKE_CXX_STANDARD 17)
以上を反映させたCMakeLists.txt
のサンプルを以下に掲載します。project
・add_executable
の()
内は適宜変更してください。また、cmake_minimum_required
の値がやや厳しく、Ubuntu 18.04やVisual Studio 2017が弾かれてしまいますが、このバージョンからデフォルトでadd_executable
に渡すソースファイルのパスとして相対パスが使えるというのが理由です。foobar.exe.manifest
に関しては後述します。
cmake_minimum_required(VERSION 3.13)
add_compile_options("$<$<C_COMPILER_ID:MSVC>:/utf-8>")
add_compile_options("$<$<CXX_COMPILER_ID:MSVC>:/utf-8>")
add_compile_options("$<$<CXX_COMPILER_ID:MSVC>:/Zc:__cplusplus>")
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
project(foobar CXX)
add_executable(foobar foobar.cpp foobar.exe.manifest)
CMake以外の他の手段としてはqmake・Meson・Blazeあたりでしょうか。Qtを使うならqmakeもなくはないです。(一応CMakeでもQtのコンパイルは可能ですが、QtStudioからの新規作成ができなかったような気がする)
Windowsでもワイド文字を使わずにUTF-8を扱う
戦略は2つあります。
- Windowsに限り、UTF-8の入出力をUTF-16のワイド文字の間で変換する
- 通常文字の入出力をShift-JISからUTF-8にする
Windowsに限り、UTF-8の入出力をUTF-16のワイド文字の間で変換する
互換性を重視するならば、この方法がよいでしょう。Boost.Nowideという非常に便利なライブラリがあります。これは、Shift-JISのプログラム引数(argv
)や、他OSと非互換のワイド文字版のコンソール・ファイルI/OをUTF-8版のもので隠してくれます。
次のようなコードで、UTF-8対応は完了します。
#include <nowide/args.hpp>
#include <nowide/iostream.hpp>
//#include <nowide/fstream.hpp> // ファイルの入出力をする場合
int main(int argc, char **argv) {
nowide::args _(argc, argv); // インスタンス「_」が生存している間argvはUTF-8版のものを指す
nowide::cout << "日本語だよ\n"; // nowide::coutの中で「std::wcout << L"日本語だよ\n"」相当の処理がされる
for(int i = 0; i < argc; ++i) {
nowide::cout << "引数" << i << ": " << argv[i] << '\n';
}
return 0; // main終了直前、「_」が破棄され、argvは元のShift-JIS版に戻る
}
問題はライブラリのビルド・リンクですが、VcpkgというC++ライブラリ用のパッケージマネージャで一発インストール可能です。公式ページの手順でインストールしたら、次のコマンドを実行してインストールします。
.\vcpkg install nowide:x64-windows-static
その後、CMakeLists.txt
に次の2行を足します。[foobar]
を実行ファイル名にリネームすることをお忘れなく。2行の間にadd_executable
が入るような形がよいかもしれません。
find_package(nowide CONFIG REQUIRED)
target_link_libraries([foobar] PRIVATE nowide::nowide)
ビルドは次のように行います。
mkdir build
cd build
cmake .. -G Ninja -DCMAKE_TOOLCHAIN_FILE=[Vcpkgのルート]\scripts\buildsystems\vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-windows-static
cmake --build .
通常文字の入出力をShift-JISからUTF-8にする
おそらくWindows10専用の手法だと思いますが、今やWindows7・8は空気になりつつあるので無視します。
引数を使う場合
setlocale
・std::locale::global
などに空文字のデフォルトロケールを渡します。
#include <locale.h>
#include <stdio.h>
int main(int argc, char **argv) {
setlocale(LC_ALL, "");
puts("日本語だよ");
for(int i = 0; i < argc; ++i) {
printf("引数%d: %s\n", i, argv[i]);
}
return 0;
}
#include <locale>
#include <iostream>
int main() {
std::locale::global(std::locale(""));
std::cout << "日本語だよ\n";
for(int i = 0; i < argc; ++i) {
std::cout << "引数" << i << ": " << argv[i] << '\n';
}
return 0;
}
そして、以下のマニフェストファイル
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<application>
<windowsSettings>
<activeCodePage xmlns="http://schemas.microsoft.com/SMI/2019/WindowsSettings">UTF-8</activeCodePage>
</windowsSettings>
</application>
</assembly>
を埋め込みます。CMakeを利用する場合、ソースファイルと一緒にadd_executable
に渡すだけでOKです。
このページを見なくても、自作のコマンドラインツールでマニフェストファイルをファイルに出力することができます。急いで作ったのでソース汚い、コマンド名微妙と散々ですが
Rustのインストールが済んでいれば以下の2行で生成完了です。Cargoという配布手段がなかったらRustなんて使っていない
cargo install --git https://github.com/tats-u/forceu8exe.git
forceu8exe manifest .\foobar.exe.manifest
引数は無視する場合(非推奨)
以下のmain()
直後の文を丸パクすればOKです。マニフェストは不要です。このプログラムをコンパイルすれば、chcp 932
なWindows(MSVC)でも、Linuxでも、文字化けせず「日本語だよ」と表示されます。
#include <locale.h>
#include <stdio.h>
int main(void) {
setlocale(LC_ALL,
#if _WIN32 && !__MINGW32__ && !__CYGWIN__
".UTF-8"
#else
""
#endif
);
puts("日本語だよ");
return 0;
}
#include <locale>
#include <iostream>
int main() {
std::locale::global(std::locale(
#if _WIN32 && !__MINGW32__ && !__CYGWIN__
".UTF-8"
#else
""
#endif
));
std::cout << "日本語だよ\n";
return 0;
}
WindowsとUnix系OSとのロケール・文字種の違い
Unix系では、setlocale
で文字コードを指定すると、ワイド文字(wchar_t
)の文字列をどのエンコードに従ってマルチバイト文字列(char
)に変換するかが変わります。setlocale
はしてもしなくても、またどのような値を指定しても、マルチバイト文字列はそのまま(ソースコードに書いたまま)出力されます。また、現在ではマルチバイト文字のエンコーディングはほぼUTF-8固定なので、マルチバイト文字でも問題が起こらないと言えます。
Windowsでは逆です。Windows(NT・2000以降)はワイド文字がメインかつ内部処理はワイド文字(UTF-16)固定で、マルチバイト文字APIは環境によってエンコーディングが変わります。Windows10限定の特殊な設定をしない限り、例えば日本語圏ではShift-JIS、英語圏ではLatin-1です。なので、マルチバイト文字をうかつに扱えません。UTF-8が使えるようになったのはWindows10になってからです。無理やり1つのエンコーディングに固定することもできますが、Windows8.1以前か、そのエンコーディングが表す言語以外か、どちらかを切り捨てざるを得ませんでした。MSもマルチバイト文字を推奨しています。
今までは、MSVCもGCCも両方使うC++erはこの2つの作法の板挟みになっていました。しかもWindowsとそれ以外ではワイド文字の長さも違います。(Windowsが2バイト、他は4バイト)絵文字などが混じったら目も当てられません。
大人しく環境によって2つの文字を使い分ける、Qtの文字クラスに頼る、あるいは日本語または絵文字(や𠮷など一部の漢字)の取り扱いを諦めるなど、あの手この手でごまかしてきました。
Windows10でのUTF-8の扱いの改善
Windows10では、マルチバイト文字のエンコーディングをUTF-8にできるようになりました。コマンドchcp 65001
によって開いているコンソールの入出力をUTF-8にするのが実用的になったのが最初で、後にOSデフォルトのエンコーディングもShift-JISではなくUTF-8にできるようになりました。
残る問題は個別ソフトの対応です。SJISの環境でもUTF-8の環境でもUTF-8で出力された日本語を表示できるようになればいいのですが、その方法は長らくマイナーでした。