某所でC++11のput_moneyで盛り上がっていたので、それに関連してC++03だとこうなるという話を書きます。

まず、C++11だとこんな風に書けるのです。

#include <iostream>
#include <iomanip>
#include <locale>
 
int main()
{
  std::string loc;
  std::cin >> loc;
 
  std::locale l(loc.c_str());
  std::cout.imbue(l);
 
  double x = 123456.789;
  std::cout << std::put_money(x, false) << std::endl;
}

put_moneyはlocaleに従って金額の書式で出力するマニピュレータです。

C++03でも、localeを直接叩けば同じ出力が得られます。

#include <iostream>
#include <locale>
 
int main()
{
  std::string loc;
  std::cin >> loc;
 
  std::locale l(loc.c_str());
  std::cout.imbue(l);
 
  double x = 123456.789;
 
  std::money_put<char> const& m =
    std::use_facet<std::money_put<char>>(l);
  m.put(std::cout, false, std::cout, ' ', x);
  std::cout << std::endl;
}

なお、この金額書式の情報は、C++で新規に登場したのではなく、Cでも存在しました。CだとLC_MONETARYのsetlocaleとlocaleconv/lconvです。

以前、bindを作りたいという話を書きました(参考:Boost.Fusionを使ってライブラリを書く)。それを思い出して、えいやっと書いてしまいました。

例によって、ソースです: bind-1.cpp

Boost.Ref使っているのはご愛嬌、さすがにref/cref (ference_wrapper)はbindと比べれば簡単に作れます。また、以前のFuisonの話のときとは、随分と見た目(雰囲気)が違いますが、やっていることはそう変わりないつもりです。

1度自分で書いてしまえば、あとはBoostなど各種実装を覗く気にもなるというものです。

前の記事では、Viusal C++の例でstd::localeのコンストラクタやstd::setlocaleに対して”jpn_jpn.932″や”deu_deu.1252″という文字列を渡していました。これについての補足です。

答えはMSDNに載っており、言語および国/地域識別文字列にあります。そのページの下の言語識別文字列国/地域識別文字列でjpnやdeuが見つけられます。たとえば、言語識別文字列で「日本語」の欄には「”japanese” または “jpn”」と書いてあります。9321252はもちろんWindowsコードページです。

つまり、”jpn_jpn.932″と”deu_deu.1252″をUnix風に書けば、それぞれ”ja_JP.SJIS”と”de_DE.ISO-8859-1″となると言うわけです。

なお、よくstd::locale(“japanese”)という記述を見かけますが、これも一応間違ってはいないです。先ほどのMSDNライブラリのsetlocale、_wsetlocaleのページに載っているように、国・地域とコードページ指定は省略可能です。

std::locale(“japanese”)は「日本語出力のため」と、おまじないのように扱われていますが、Shift_JISで表現された文字列を出力するという目的であれば、std::locale(“.932″)のほうが適切なのに、と思います……。なお、私は特に必要のない場合CP_ACPとの一貫性をとってstd::locale(“”)派ですが。

C++11のto_string関数について調べていました。タイトルに書いたとおり、locale指定の影響と、書式の2つを知りたくなったためです。

このコードを動かしてみましょう。これは、Linux上のGCC 4.6.2 (glibc + libstdc++)で動かしました。

#include <iostream>
#include <string>
#include <locale>
#include <clocale>
#include <cstdio>
 
int main()
{
	double x = 1234.56;
 
	std::cout.imbue(std::locale::classic());
	std::setlocale(LC_ALL, "C");
	std::cout << "C:\n";
	std::cout << '\t' << x << '\n';
	std::cout << '\t' << std::to_string(x) << std::endl;
	std::printf("\t%f\n", x);
 
	std::cout.imbue(std::locale("ja_JP.UTF-8"));
	std::setlocale(LC_ALL, "ja_JP.UTF-8");
	std::cout << "ja_JP.UTF-8:\n";
	std::cout << '\t' << x << '\n';
	std::cout << '\t' << std::to_string(x) << std::endl;
	std::printf("\t%f\n", x);
 
	std::cout.imbue(std::locale("de_DE.UTF-8"));
	std::setlocale(LC_ALL, "de_DE.UTF-8");
	std::cout << "de_DE.UTF-8:\n";
	std::cout << '\t' << x << '\n';
	std::cout << '\t' << std::to_string(x) << std::endl;
	std::printf("\t%f\n", x);
}

こっちは、Windows上のVisual C++ 2010です。

#include <iostream>
#include <string>
#include <locale>
#include <clocale>
#include <cstdio>
 
int main()
{
	long double x = 1234.56;
 
	std::cout.imbue(std::locale::classic());
	std::setlocale(LC_ALL, "C");
	std::cout << "C:\n";
	std::cout << '\t' << x << '\n';
	std::cout << '\t' << std::to_string(x) << std::endl;
	std::printf("\t%f\n", x);
 
	std::cout.imbue(std::locale("jpn_jpn.932"));
	std::setlocale(LC_ALL, "jpn_jpn.932");
	std::cout << "jpn_jpn.932:\n";
	std::cout << '\t' << x << '\n';
	std::cout << '\t' << std::to_string(x) << std::endl;
	std::printf("\t%f\n", x);
 
	std::cout.imbue(std::locale("deu_deu.1252"));
	std::setlocale(LC_ALL, "deu_deu.1252");
	std::cout << "deu_deu.1252:\n";
	std::cout << '\t' << x << '\n';
	std::cout << '\t' << std::to_string(x) << std::endl;
	std::printf("\t%f\n", x);
 
	std::cout << std::endl;
}

なお、VC++版でlong doubleを使っている理由は、Visual C++ 2010の実装が不完全なためです(参考: [C++0X] std::to_string is non conforming | Microsoft Connect)。

de_DE/deu_deuは、Cやja_JP/jpn_jpnと小数点・桁区切りの記号が異なるロケールの代表例として選びました。

実行結果はそれぞれこうなりました。まずはLinux/GCC/libstdc++版です。

C:
        1234.56
        1234.560000
        1234.560000
ja_JP.UTF-8:
        1,234.56
        1234.560000
        1234.560000
de_DE.UTF-8:
        1.234,56
        1234,560000
        1234,560000

続いて、Visual C++版です。

C:
        1234.56
        1234.56
        1234.560000
jpn_jpn.932:
        1,234.56
        1234.56
        1234.560000
deu_deu.1252:
        1.234,56
        1234,56
        1234,560000

Visual C++はちょっと惜しいです。

printfの出力をわざわざ並べていることから分かったかもしれませんが、to_stringの結果はprintf系と同じ書式になるのが正しいようです。N3290(最終ドラフト)の21.5 Numeric conversions [string.conversions]のto_stringの欄には、型ごとにそれぞれ”%d”, “%u”, “%ld”, “%lu”, “%lld”, “%llu”, “%f”, “%f”, “%Lf”の書式でsprintfを呼んで文字列を作るというようなことが書いてあります。実は、その記述を見つけたので上記のプログラムを書いて試したという順序でした。

あとは、boost::lexical_castもついでに試しておけば良かったですね。次への課題とします。

DoxygenのLaTeX出力は、普通のLaTeXもしくはpdfLaTeXを想定しており、そのままでは日本語が使えません。そのため、日本語を含むドキュメントを最低限文字化けせずにPDF化する方法を調べていました。

  • 必要なもののインストール(初回だけ)
  • Doxyfileの修正(プロジェクトごとに1回だけ)
  • Makefileとrefman.texの修正(doxygenコマンドを実行するたび)

まず、以下のものが必要でした。

Doxygen
この記事を書いている時点での最新は1.7.6.1でした。しかし、この方法自体は1年くらい前からやり始めたので、もう少し前のバージョンでも可能だと思います。
pLaTeXまたはupLaTeX
WindowsなのでW32TeXを使っています。私はインストール(Windows) – TeX Wikiの「標準インストールで必要なもの」一式とuptex-w32.tar.xzを導入した状態です(OTFパッケージも入れていますが今回は使っていません)。もちろん、pLaTeXを使用するならuptex-w32.tar.xzは不要です。Unix系ならTeX Live 2011などで良いと思います
Cygwin
なくても大丈夫です。私は、Doxygenの生成するMakefileの実行にGNUのmakeを使うためと、ファイルの修正にsedを使うためにCygwinを使っています。

このほか、以下のパッケージが必要でした。Doxygenが生成するLaTeXソース内でこれらが\usepackageされています。


準備ができたら、Doxyfileの編集です。doxygen -gやDoxywizardなどで生成したDoxyfileに対して以下の2ヶ所を修正します。

LATEX_CMD_NAME
LATEX_CMD_NAME = uplatex

platexを使う場合は以下のようにします。その場合、OTFパッケージとUTF-8 入力での利用 – OTF – TeX Wikiにある補助プログラムの併用を考えても良いかもしれません。

LATEX_CMD_NAME = "platex -kanji=utf8"
USE_PDFLATEX
USE_PDFLATEX = NO

現在のところ、pdfLaTeXは日本語環境で使えません。そのため、NOを選択しておきます。この設定、YESでもNOでも日本語環境に適さないという点では同じですけどね。

このほか、もちろんGENERATE_LATEXがYESになっていなければYESにしましょう。


ここまできたら、doxygenコマンドを実行します。ただし、これで終わりではありません。生成されたファイルに、さらに修正を加える必要があります。毎回行うのは面倒なので、適当なスクリプトにでもまとめておきましょう。

Makefileのdel

Makefileの最下行の「del /s/y ……」を「rm -f ……」に直します。ただし、Windows版以外では、この必要はないかもしれません。

$ sed -i 's|del /s/y|rm -f|' Makefile
refman.texにPDFのしおりに関する指定を追加

2行目に以下の内容を追加します。この記述内容についてはhyperref – TeX Wikiを参考にしてください。

\ifx\kanjiskip\undefined\else
  \usepackage{atbegshi}
  \ifx\ucs\undefined
    \ifnum 42146=\euc"A4A2
      \AtBeginShipoutFirst{\special{pdf:tounicode EUC-UCS2}}
    \else
      \AtBeginShipoutFirst{\special{pdf:tounicode 90ms-RKSJ-UCS2}}
    \fi
  \else
    \AtBeginShipoutFirst{\special{pdf:tounicode UTF8-UCS2}}
  \fi
\fi
refman.texでhyperrefのオプションをps2pdfからdvipdfmxに変える

ついでに、graphicsxやxcolorなどもdvipdfmxの指定を追加したほうが良いのかもしれません。

refman.texでhyperrefのオプションからunicodeを消去する

元がこうであるところ、

\usepackage[dvipdfmx,
            pagebackref=true,
            colorlinks=true,
            linkcolor=blue,
            unicode
           ]{hyperref}

下のように変えます。

\usepackage[dvipdfmx,
            pagebackref=true,
            colorlinks=true,
            linkcolor=blue
           ]{hyperref}
\usepackage{pspicture}の行を削除する

実際には使っていないらしく、削除しても特に問題ありませんでした。削除する理由は、これがあるとdvipdfmxで生成できないためです。Doxygenのバージョンが新しいと、そもそもこの行が生成されないのでこの手順は不要です。

本文をゴシック体にする

本文がサンセリフ体となっているため、そのままだと「和文:明朝体、欧文:サンセリフ体」というアンバランスな組み合わせになってしまいます。最低限の対処として、本文の和文書体をゴシック体にすることしました。\renewcommand{\kanjifamilydefault}{\gtdefault}を書き加えると良いようです(参考:スライドなどで数式をサンセリフ体にする – 西方研Wiki)。

上に書いたrefman.texへの修正をsedでやるとこんな感じでしょうか。

1a\\\ifx\\kanjiskip\\undefined\\else\
  \\usepackage{atbegshi}\
  \\ifx\\ucs\\undefined\
    \\ifnum 42146=\\euc"A4A2\
      \\AtBeginShipoutFirst{\\special{pdf:tounicode EUC-UCS2}}\
    \\else\
      \\AtBeginShipoutFirst{\\special{pdf:tounicode 90ms-RKSJ-UCS2}}\
    \\fi\
  \\else\
    \\AtBeginShipoutFirst{\\special{pdf:tounicode UTF8-UCS2}}\
  \\fi\
\\fi
s/usepackage\[ps2pdf,/usepackage[dvipdfmx,/
/unicode/d
s/linkcolor=blue,/linkcolor=blue/
/\\usepackage{pspicture}/d
1a\\\renewcommand{\\kanjifamilydefault}{\\gtdefault}

上記内容をrefman.sedというファイル名で保存したら、以下のように実行できます。

$ sed -f refman.sed -i refman.tex

ここまでができたら、makeとdvipdfmxを実行します。

$ make && dvipdfmx refman.dvi

Cygwinがないなど、makeを使わないなら適当に(u)platexを実行すれば同じです。Makefileの中でやっていることはだいたいこんな感じです。

$ uplatex refman.tex
$ makeindex refman.idx
$ uplatex refman.tex
$ uplatex refman.tex
$ dvipdfmx refman.dvi

platexでもだいたい同じです。

$ platex -kanji=utf8 refman.tex
$ makeindex refman.idx
$ platex -kanji=utf8 refman.tex
$ platex -kanji=utf8 refman.tex
$ dvipdfmx refman.dvi

refman.texの修正の仕方は、refman.tex実物を1度見て、どう直すか考えるのが良いと思います。DoxygenのバージョンやDoxyfileでの設定によって出力が変わるので、この手順がそのまま適用できるとは限らないので。

本記事は、Boost Advent Calendar 2011の17日目の記事です。


きっかけは、「bind(BoostやTR1やC++11にあるやつ)を書いてみよう」と思い立ったことでした。

常日頃から”shared_ptr”, “function”, “bind”が三種の神器であると考えている私ですが、今までbindだけは自分で作ったことがなく、また作り方も分からなかったのです。そのためBoost/TR1/C++11なしの縛りでコードを書く際は、std::bind1st/bind2ndが適用できるよう落とし込むか、その都度関数オブジェクトを作る羽目になっていました。

しかし、最近ようやく1つのやり方を思い付きました(mem_fn相当を自動でやってくれる機能は本筋ではないので省略しています)。ちなみに、未だにBoost.Bindのソースコードは見たことがありません。

  1. Bind関数では、引数リストをタプルに保管したbinderオブジェクトを返す(bind(f, foo, _1, bar, _2)なら{foo, _1, bar}というタプルにする)。
  2. operator ()では次のことを行う。

    1. binderのタプルから_1(プレースホルダ)に1番目の引数を当てはめたタプルを作る。
    2. 上のタプルから_2に2番目の引数を当てはめたタプルを作る。
    3. 「上の_NにN番目の引数を当てはめたタプルを作る」を引数が尽きるまで繰り返す
    4. 上のタプルを引数並びとして関数を呼び出す。

「タプルを操作する」といえばBoost.Fusionです。とりあえずFusionを使って、上記のようなプログラムが書けることを試すことにしました。ここまで、前書きでした。今回の話にはbindを作ること自体は特に関係ありません。


Boost.Fusionを使ってライブラリを作るということは、戻り値の型を書くこととの戦いと言っても過言ではありません。これが今日の本題です。

そのbindを書いていて次のようなコードが出てきました。

template<
  std::size_t N,
  typename ArgsSequence,
  typename Args2Sequence,
  typename boost::enable_if<
    typename mpl::empty<Args2Sequence>::type>::type*& = enabler>
(……)
invokeN(ArgsSequence const& s, Args2Sequence const&) const
{
  return fusion::invoke(m_f, s);
}

fusion::invokeは「タプル(Fusion Sequence)を引数並びとして関数を呼び出す」を実現してくれるものです。さて、invokeNメンバ関数の戻り値の型、(……)には何を当てはめればよいでしょうか。すなわち、このfusion::invokeはどんな型を返すのでしょうか。もちろん、C++11ならdecltypeが使えます(注意:以下のコードは実際にはコンパイルエラーになります)。

template<
  std::size_t N,
  typename ArgsSequence,
  typename Args2Sequence,
  typename boost::enable_if<
    typename mpl::empty<Args2Sequence>::type>::type*& = enabler>
auto invokeN(ArgsSequence const& s, Args2Sequence const&) const
  -> decltype(fusion::invoke(m_f, s))
{
  return fusion::invoke(m_f, s);
}

しかし、decltypeのないC++03ではどうしましょう?この問題に立ち向かっているのが、Boost.Fusionのもう1つの顔なのです。

基本的には、戻り値の型を別手段で提供しています。もう1度fusion::invokeの公式のページを見てみましょう:infoke

Synopsis

template<
    typename Function,
    class Sequence
    >
typename result_of::invoke<Function, Sequence>::type
invoke(Function f, Sequence & s);
 
template<
    typename Function,
    class Sequence
    >
typename result_of::invoke<Function, Sequence const>::type
invoke(Function f, Sequence const & s);

戻り値の型はtypename result_of::invoke<……>::typeとして宣言されています。そう、これがミソなのです。

Boost.Fusionにあるすべての関数は、boost::fusion::result_of::(関数名)<関数の引数の型>::typeというメタ関数呼出で戻り値の型が取得できるようになっているのです。

そうと分かれば簡単です。M_fの型はFunctorであるとして、次のように書けば良いのです。

template<
  std::size_t N,
  typename ArgsSequence,
  typename Args2Sequence,
  typename boost::enable_if<
    typename mpl::empty<Args2Sequence>::type>::type*& = enabler>
typename fusion::result_of::invoke<Functor, ArgsSequence>::type
invokeN(ArgsSequence const& s, Args2Sequence const&) const
{
  return fusion::invoke(m_f, s);
}

先ほどの(……)に果てはまる答えは、typename fusion::result_of::invoke<Functor, ArgsSequence>::typeでした。

ただし、これで完成にしてはいけません。このinvokeN関数の戻り値の型の取得手段も、やはり提供しましょう。ただ、fusionほど厳密にやらないことにします(invokeNは外部に公開する関数という位置づけではないからです)。

template<typename ArgsSequence>
struct invoke0_result :
  fusion::result_of::invoke<Functor, ArgsSequence>
{};

ArgsSequenceさえ分かれば決定できるということで、それだけしか引数にしていません。名前もresult::invokeNとしていません。

これを使って先ほどの例は次のように直します。

template<
  std::size_t N,
  typename ArgsSequence,
  typename Args2Sequence,
  typename boost::enable_if<
    typename mpl::empty<Args2Sequence>::type>::type*& = enabler>
typename invoke0_result<ArgsSequence>::type
invokeN(ArgsSequence const& s, Args2Sequence const&) const
{
  return fusion::invoke(m_f, s);
}

では、次の例にいってみましょう。

template<
  std::size_t N,
  typename ArgsSequence,
  typename Args2Sequence,
  typename boost::disable_if<
    typename mpl::empty<Args2Sequence>::type>::type*& = enabler>
(戻り値の型2)
invokeN(ArgsSequence const& s, Args2Sequence const& args2) const
{
  typedef typename boost::remove_reference<typename fusion::result_of::front<Args2Sequence>::type>::type front_type;
  return this->invokeN<N + 1>(
    fusion::transform(s, replace<placeholder<N>, front_type>{fusion::front(args2)}),
    fusion::pop_front(args2));
}

(戻り値の型2)はどう書いたら良いでしょうか。関数の中身はさっきより複雑です。

今度は、初めから戻り値型を得るメタ関数から作ることにします。

template<
  std::size_t N,
  typename ArgsSequence,
  typename Args2Sequence>
struct invokeN_result :
  (何か)
{};

さてお気づきかもしれませんが、最初の例の関数と今度の例の関数、同じ名前です。enable_ifとdisable_ifで切り替えているのです。Args2Sequenceが長さ0なら最初の関数、そうでなければ、こっちの関数を使うよう分岐させていました。なので、それを表現します。

template<
  std::size_t N,
  typename ArgsSequence,
  typename Args2Sequence>
struct invokeN_result :
  mpl::eval_if<
    typename mpl::empty<Args2Sequence>::type,
    invoke0_result<ArgsSequence>,
    (こっちのinvokeNが返す型)
  >
{};

Mpl::eval_ifの条件はenable_if/disable_ifの条件をそのまま持ってきました。そして真の場合は先ほどのinvoke0_resultがさっそく登場です。そう、このinvokeN_resultは、今度のinvokeNだけでなく、最初のinvokeNにも対応した「invokeNの戻り値を表すメタ関数」に変貌したのです。

invokeNの中身を見てみましょう。

return this->invokeN<N + 1>(

そう、再帰呼出です。なので、invokeNの戻り値の型を知るには「invokeNの戻り値を表すメタ関数」が必要という状況に陥っていたのです。

template<
  std::size_t N,
  typename ArgsSequence,
  typename Args2Sequence>
struct invokeN_result :
  mpl::eval_if<
    typename mpl::empty<Args2Sequence>::type,
    invoke0_result<ArgsSequence>,
    invokeN_result<
      N + 1,
      (……),
      (……),
    >
  >
{};

というわけで、さっそくそこを埋めました。invokeN_resultは3つ引数を取るメタ関数なので、残り2つが問題です。再びinvokeN内を見ていきましょう。

template<
  std::size_t N,
  typename ArgsSequence,
  typename Args2Sequence>
return this->invokeN<N + 1>(
  fusion::transform(s, replace<placeholder<N>, front_type>{fusion::front(args2)}),
  fusion::pop_front(args2));

では、1つ目の引数、Fusion::transformです。Sの型はArgsSequenceです。さっそく書き込みます。

template<
  std::size_t N,
  typename ArgsSequence,
  typename Args2Sequence>
struct invokeN_result :
  mpl::eval_if<
    typename mpl::empty<Args2Sequence>::type,
    invoke0_result<ArgsSequence>,
    invokeN_result<
      N + 1,
      typename fusion::result_of::transform<
        ArgsSequence,
        (……)
      >::type,
      (……)
    >
  >
{};

ここのFusion::transformの2つ目の引数replace<placeholder<N>, front_type>{fusion::front(args2)}は、replace<placeholder<N>, front_type>型の一時オブジェクトを作りfusion::front(args2)で初期化という式です(C++11の初期化構文を使用)。なので、fusion::front(args2)は知らなくても大丈夫です(とは言え、実際にはfront_typeなのですけどね)。

template<
  std::size_t N,
  typename ArgsSequence,
  typename Args2Sequence>
struct invokeN_result :
  mpl::eval_if<
    typename mpl::empty<Args2Sequence>::type,
    invoke0_result<ArgsSequence>,
    invokeN_result<
      N + 1,
      typename fusion::result_of::transform<
        ArgsSequence,
        replace<placeholder<N>, front_type>
      >::type,
      (……)
    >
  >
{};

さて、これで良いと思ったらそれは早とちりです。front_typeはinvokeN内のtypedefだったのでここでは参照できません。front_typeの定義を見て直接書き込むことにします。

template<
  std::size_t N,
  typename ArgsSequence,
  typename Args2Sequence>
struct invokeN_result :
  mpl::eval_if<
    typename mpl::empty<Args2Sequence>::type,
    invoke0_result<ArgsSequence>,
    invokeN_result<
      N + 1,
      typename fusion::result_of::transform<
        ArgsSequence,
        replace<
          placeholder<N>,
          typename boost::remove_reference<
            typename fusion::result_of::front<Args2Sequence>::type
          >::type
        >
      >::type,
      (……)
    >
  >
{};

残った(……)に対応するのは、fusion::pop_front(args2)です。もう分かりましたね。fusion::result_of::pop_frontを使います。これでinvokeN_resultが完成しました。

template<
  std::size_t N,
  typename ArgsSequence,
  typename Args2Sequence>
struct invokeN_result :
  mpl::eval_if<
    typename mpl::empty<Args2Sequence>::type,
    invoke0_result<ArgsSequence>,
    invokeN_result<
      N + 1,
      typename fusion::result_of::transform<
        ArgsSequence,
        replace<
          placeholder<N>,
          typename boost::remove_reference<
            typename fusion::result_of::front<Args2Sequence>::type
          >::type
        >
      >::type,
      typename fusion::result_of::pop_front<Args2Sequence>::type
    >
  >
{};

これを使って今度のinvokeNは次のような戻り値の型を書けば良いのです。

template<
  std::size_t N,
  typename ArgsSequence,
  typename Args2Sequence,
  typename boost::disable_if<
    typename mpl::empty<Args2Sequence>::type>::type*& = enabler>
typename invokeN_result<N, ArgsSequence, Args2Sequence>::type
invokeN(ArgsSequence const& s, Args2Sequence const& args2) const

結論です。

  • 戻り値の型は、Return文の内容と一対一で対応(マッピング)させながら、戻り値の型を取得するメタ関数呼出を記述していけば出来上がる。
  • 自らの作った関数についても、戻り値の型を取得するメタ関数を定義する。
  • 実は、Boost.Fusionを使う場合に限らず、C++11のdecltypeを使わないBoost.ResultOfを使ったり、それに対応(サポート)する場合のクラステンプレートresultを作ったりする場合もこの考えで大丈夫です。むしろ、今回の話はBoost.FusionよりもBoost.ResultOfを題に冠すべき記事だったのです。ResultOfさん、ごめんなさい。

    では、最後に今日のソースコード全体へのリンクを張っておしまいにします:bind-fusion-1.cpp。g++ 4.6.2でコンパイルしていました。

本記事は、JavaScript Advent Calendar 2011 (オレ標準コース)の14日目の記事です。


Windowsに付属するJavaScriptエンジンとしてJScript.dllがあります(いやJScriptと名乗っていますけど)。

Internet Explorer(8まで)などが使っているほか、APIが公開されており自分のアプリケーションからも呼び出せるのはご存じと思います。そのやり方はググればいくらでも例が見つかりますので、今日はその話ではありません。

JScript.dllは、いくつかバージョンがありますが最新は5.8です。しかし、5.8には5.7互換モードがあり、自アプリケーションでJScript.dllをホストする場合、デフォルトでは5.7モードで動いてしまうのです(IE8をインストールしても、IEコンポーネントはIE7互換モードで動いてしまうのと似た話ですね)。

というわけで、5.8で動かすには5.8モードにする指定が必要です。

IActiveScriptPtr js(L"JScript");
IActiveScriptPropertyPtr jsProperty(js);
 
_variant_t v(implicit_cast<long>(SCRIPTLANGUAGEVERSION_5_8), VT_I4); // ATL::CComVarinatでも可。
auto hr = jsProperty->SetProperty(SCRIPTPROP_INVOKEVERSIONING, nullptr, &v);

やっておいて損はないので、JScript.dllを自アプリに組み込もうという場合は忘れずにやっておきましょう。

サンプルのプログラムです: JScript5.8.cpp。Visual C++ 2010とWindows SDK 7.1を使ってコンパイル・実行しました。ATLは使っていないので、Visual C++ Expressでもコンパイルできるはずです。

最近のReactive ExtensionsにはToEventというのがあるのを知りました(via Reactive Extensions再入門 その15「To*****系メソッド」 – かずきのBlog@Hatena)。調べてみるとToEventPatternがあることも分かりました。「これってもしかしてIObservable<T>から.NETのイベントを発行するのに使えるのではないか」と思い、早速試してみたところうまくいきました。

class Program
{
    static void Main()
    {
        var t = new Test();
 
        t.Hoge += (sender, e) => Console.WriteLine("Hoge event!");
        t.FireHoge();
        t.FireHoge();
    }
}
 
class Test
{
    public Test()
    {
        ee = se.Select(e => new EventPattern<EventArgs>(this, e)).ToEventPattern();
    }
 
    public void FireHoge()
    {
        se.OnNext(EventArgs.Empty);
    }
 
    readonly Subject<EventArgs> se = new Subject<EventArgs>();
    readonly IEventPatternSource<EventArgs> ee;
 
    public event EventHandler<EventArgs> Hoge
    {
        add
        {
            ee.OnNext += value;
        }
        remove
        {
            ee.OnNext -= value;
        }
    }
}

内部ではRxガンガン使うけど、表向きには.NETのイベントとして公開したいという場合に便利ですね。Windows Phone標準搭載のAPIにもこれ欲しかったです。

参考: Observable.ToEventPattern(TEventArgs) Method (System.Reactive.Linq) (MSDN Library)

本記事は、Windows Phone Advent Calendar 2011 : ATNDの7日目の記事です。


True Audio (TTA)という可逆圧縮の音声形式があります。はい、つまりWindows PhoneでTTAの再生ができるかどうかちょっと試してみました。結論から言うと、ハマりどころもなく簡単にできました(まだいろいろと工夫の余地はありますが)。

公式サイトのFree Downloadsを見ると、CとC++で書かれたライブラリがあるようです。迷わずC++版 (libtta++) をダウンロードしました。中身を確認すると、静的LIBを作るVC++プロジェクトとサンプルのコンソールアプリのVC++プロジェクトが入っていました。

ではここで、Windows Phone OS 7.1ネイティブコードプログラミングのおさらいです。

  1. Visual Studio 2005 or 2008でWindows Mobile 6.5.3 DTKターゲットのDLLプロジェクトで、普通のCOMコンポーネントを作る要領でコードを書く(参考)。
  2. Visual Studio 2010でWindows Phone OS 7.1プロジェクトを作る。
  3. VS2010のプロジェクトに1.でビルドしたDLLとWPInteropManifest.xmlを追加する(参考:Windows Phone 7.5 ネイティブコードプログラミング – ななふぉ)。
  4. VS2010プロジェクト側、C#上で1.で作ったインタフェースやクラスに対応する宣言を書く。RegisterComDllの呼び出しを行う。ネイティブDLLで作ったCOMクラスをnewする。

今回はlibtta++の静的LIBをビルドするプロジェクトを作成、1.のDLLプロジェクトでその静的LIBをリンクするようにしました。

libtta++のビルドは、Visual Studio 2008でやはりWindows Mobile 6.5.3 DTKターゲットの空のプロジェクト(スタティックライブラリ)を新規に作ってやりました。

  • そのプロジェクトに、libtta.cppを追加。
  • あとconfig.hを開いて、#define CPU_X86の行をコメントアウト。

Releaseビルドしたら、libtta.libが得られました。なお、付属のVC++プロジェクトを開いてWindows Mobile 6.5.3 DTKの構成を追加する方法はリンク時によく分からないエラーがでてどうしようもなかったので諦めました。

次にDLLのプロジェクトです。libtta.libのほか、libtta++からconfig.hとlibtta.hを持ってきて使用しています。最後にソースファイル一式ダウンロードのリンクを置くので、詳しくはそちらを見てください。なんの工夫もなく、libtta++付属サンプルを改造し「TTAデコード→WAVをメモリに書き出す→そのメモリを呼び出し元に返す」というプログラムです。

とりあえず、インタフェースの宣言だけここに載せます。

interface __declspec(uuid("95E19FD5-D011-4979-BBFB-26D9C358BDC0"))
IDebug : public IUnknown
{
    virtual HRESULT STDMETHODCALLTYPE WriteLine(BSTR s) = 0;
    virtual HRESULT STDMETHODCALLTYPE WriteLineW(LPCWSTR s) = 0;
};
 
interface __declspec(uuid("36F21A71-822B-4CD5-9DC9-F1A68FE6AE04"))
IDecoder : IUnknown
{
    virtual HRESULT STDMETHODCALLTYPE Init(
        /*[in]*/ IDebug* debug) = 0;
    virtual HRESULT STDMETHODCALLTYPE Decode(
        /*[in]*/ BYTE* source, /*[in]*/ int length,
        /*[out, ret]*/ SAFEARRAY** dest) = 0;
};

IDebugについては、OutputDebugStringの代替です(参考:WP7.1ネイティブとOutputDebugString)。

これもReleaseビルドします。CycmNativeという名前のVC++プロジェクトだったので、CycmNative.dllが出来上がります。

いよいよ、Windows Phone OS 7.1プロジェクトの作成です。CycmNative.dllと適当なTTAファイルをプロジェクトに追加します(今回は、TTAファイルを「ビルドアクション:コンテンツ」で埋め込みました)。あと、WPInteropManifest.xmlは以下の内容です。もしかして、空っぽでもイケるんですかね?

<Interop/>

C#側のメイン部分のコードは短いのですべてここに載っけてしまいます(もちろん、この記事の最後にプロジェクト一式アーカイブのリンクを用意しています)。

public partial class MainPage : PhoneApplicationPage
{
    public MainPage()
    {
        InitializeComponent();
    }
 
    private void PhoneApplicationPage_Loaded(object sender, RoutedEventArgs e)
    {
        ComHelper.Register("cycmnative.dll", typeof(DecoderImpl));
 
        var decoder = new IDecoder();
        decoder.Init(new DebugImpl());
 
        var rs = Application.GetResourceStream(new Uri("cycm.tta", UriKind.Relative));
        byte[] buf = new byte[rs.Stream.Length];
        rs.Stream.Read(buf, 0, buf.Length);
        var result = decoder.Decode(buf, buf.Length);
 
        sound = SoundEffect.FromStream(new MemoryStream(result));
        soundInstance = sound.CreateInstance();
        SoundEffect.MasterVolume = 1.0f;
        soundInstance.Play();
    }
 
    SoundEffect sound;
    SoundEffectInstance soundInstance;
}

デコードしたらWAVデータが得られるようになっているので、あとはSoundEffectで再生させているだけです。はい、一気にデコードさせるのではなく、デコードしつつ再生するようにしたいですね。たぶん、MediaStreamSourceを使えばできるのだと思います。

以上、大変駆け足でのWidnows Phone OS 7.1ネイティブコードプログラミングでした。ID_CAP_INTEROPSERVICES? 何それ?おいしいの?

  • CycmNative.zip(ネイティブ側VC++2008プロジェクト、libtta++にあわせてLGPLv3)
  • Cycm.zip(マネージ側VC#2010プロジェクト、MIT Lisence)

本記事は、C++11 Advent Calendar 2011 : ATNDの2日目の記事です。


C++のclassは様々な使い方ができます。後発のほかの言語ではいくつもの概念に分かれているものも、C++ではすべてclassということもあります。

そこで、C++でclassを定義する際も、classと一括りにせず、自分がいったいどんなclassを書こうとしているのか明確に意識するとよいのではないだろうかと考えました。そのために、私なりのclassの分類をまとめ、この記事を書くことにしました。

これは、各々のプログラミング言語の経験により違いが出ることと思います。異論もあると思いますので、ご自身でも考えてみると良いと思います。


1つ目は「オブジェクト指向プログラミング (OOP) を実現するクラス」です(長いので以下OOPクラスと略します)。

virtual関数を使い、クラスの継承を用いて、多態性を表現するプログラミングスタイルを実現するクラスです。基本的に、これに分類するclass同士の継承は単独継承(すべてpublic)のみです。後述するインタフェースやMix-inのclassを1つまたは複数個追加で継承することは構いません。厳密に言うと多重継承は禁止ではありませんが、istream/ostream←iostreamのようにうまいことハマる例は珍しいと思います。

class Widget
{
public:
    int GetId();
    virtual void Draw() = 0;
    virtual Rectangle GetBound();
    virtual ~Widget() = default;
 
private:
    std::unique_ptr<graphic_t> graphic_t;
    std::unique_ptr<window_handle_t> window_handle;
 
    Widget(Widget const&) = delete;
    Widget(Widget&&) = delete;
    Widget& operator =(Widget const&) = delete;
    Widget& operator =(Widget&&) = delete;
};
 
class TextBox : Widget
{
public:
    virtual void Draw() override;
    virtual Rectangle GetBound();
    virtual ~TextBox() = default;
 
private:
    std::u16string text;
};
 
// ……
デストラクタは原則としてdefaultとし、コンパイラの自動生成に任せます。
リソースの解放処理が必要なら、必ずそれをラップした型を作り、それをメンバとして持つようにします。デバッグログを出力するなどと言った解放処理とあまり関係ない目的ならデストラクタを明示的に書いても良いでしょう。なお、これは以下すべての分類のclassに当てはまります。
デストラクタはpublicでvirutalにします。
「非virtualでprotected」や「非virutalでpublic」のclassは、たぶんOOPクラスに当てはまりません
コピーコンストラクタ・コピー代入演算子・ムーブコンストラクタ・ムーブ代入演算子をdeleteします
この種のclassではオブジェクトのコピーはどういう意味を持つのか考えられないことが多いです。あと、スライシングが問題になるという現実もあります。なお、従来通りboost::noncopyableから派生させる方法でも構いません。必要なら、Javaなどオブジェクトへのポインタを持ち運ぶ言語を見習い、newしてunique_ptrまたはmake_sharedしてshared_ptrに入れましょう。

最後のコピー禁止にする話の補足です。希に、コピーを作れるようにしたい場合もあるとは思います。その場合は、JavaやC#のようにCloneメンバ関数を「virtualで」定義しましょう。その場合、Cloneメンバ関数の実装を目的として、protectedなコピーコンストラクタを定義しても構いません。

class Node
{
public:
    virtual std::unique_ptr<Node> Clone();
    ~Node() = default;
 
protected:
    Node(Node const&) = default;
 
private:
    std::string name;
 
    Node(Node&&) = delete;
    Node& operator =(Node const&) = delete;
    Node& operator =(Node&&) = delete;
};
 
class TextNode : public Node
{
public:
    virtual std::unique_ptr<TextNode> Clone() override;
 
protected:
    TextNode(TextNode const&);
};
 
std::unique_ptr<TextNode> TextNode::Clone()
{
    return {new TextNode(*this);};
}

2つ目、「インタフェース」です。

JavaとかC#とかのinterfaceです。C++だと純粋仮想関数を並べたクラスとして表現します。

class IHogeable
{
public:
    virtual void Foo(int x, int y) = 0;
 
    virtual ~IHogeable() = default;
 
private:
    IHogeable(const IHogeable&) = delete;
    IHogeable(const&&) = delete;
    IHogeable& operator =(const IHogeable&) = delete;
    IHogeable& operator =(const&&) = delete;
};

名前にIを付けているのは、私の趣味(COMや.NET由来)なので真似なくて構いません。

インタフェースは、他のインタフェースを複数継承しても構いません。OOPクラスは1つあるいは複数のインタフェースを継承して構いません。インタフェースがOOPクラスあるいは他のclassを継承するのはあり得ません(boost::noncopyableのような例外はありますが)。

デストラクタをpublicでvirtualに、コピー・ムーブ禁止であるところはOOPクラスと同じです。

実際のところ、C++で純粋仮想関数ばかりを並べたインタフェースというのはあまり作らないように思います。大抵、NVIパターン (Non-Virtual Interface Idiom)で作ると思うのです(NVIについてはNon-Virtual Interface Idiom – Radium Software Developmentなどを参照してください)。これは、このインタフェースに分類すべきか次のMix-inに分類すべきか若干悩ましいです。

class IHogeable
{
public:
    void Foo(int x, int y)
    {
        if (x < 0 || y < 0)
        {
            throw new out_of_range("……");
        }
        FooImpl(x, y);
    }
 
    void Foo(Point pt)
    {
        return Foo(pt.X, pt.Y);
    }
 
    virtual ~IHogeable() = default;
 
protected:
    virtual void FooImpl(int x, int y) = 0;
 
private:
    IHogeable(const IHogeable&) = delete;
    IHogeable(IHogeable&&) = delete;
    IHogeable& operator =(const IHogeable&) = delete;
    IHogeable& operator =(IHogeable&&) = delete;
};

3つ目、「Mix-in」です。

制約を課して多重継承を使うという考え方といって合っているでしょうかね。私が最初に見たまともな説明はまつもと直伝 プログラミングのオキテ 第3回(3) – まつもと直伝 プログラミングのオキテ:ITproだったような気がします。ここには、Mix-inについて次のように書いてあります。

Mix-inというのは元々Lisp界で始まった多重継承の使い方です。Mix-in手法には次の2つの条件があります。

  • 通常の継承は単一継承に限る
  • 2つめ以降の継承は,Mix-inと呼ばれる抽象クラスからに限定する

Mix-inクラスは以下のような特徴を備えた抽象クラスです。

  • 単独でインスタンスを作らない
  • 通常のクラスから継承しない

私としては、Mix-in classへアップキャストしての(実行時の)多態性は必要としないという印象があります。そのため、Mix-in classではprotected非virtualデストラクタとします。Mix-in classを継承する際はprotectedまたはprivate継承という場合もあります。また、CRTP (Curiously recurring template pattern)との相性も良いです。

Mix-inのclassは、インタフェース同様1つあるいは複数のMix-inから派生して構いません。また、インタフェースを1つまたは複数継承することも構いません。

コピー・ムーブのコンストラクタ・代入演算子はケースバイケースです。というのも、このMix-inのclass、用途によってはOOPクラスのほか、後述するデータ抽象のclassが継承することもあり得るためです。次の可能性があり得るでしょう。

  • すべてdelete(OOPクラス・インタフェース同様)
  • ムーブのみprotectedで定義
  • ムーブ・コピーをprotectedで定義

いずれにせよ、定義する場合は原則としてdefaultで定義しましょう、と言う点は変わりません。

Boost.OperatorsやBoost.Noncopyableもここに分類します。

class IUnknownCounter
{
    long AddRefImpl()
    {
        return count++;
    }
 
    long ReleaseImpl()
    {
        long ret = --count;
        if (ret == 0)
        {
            delete this;
        }
        return ret;
    }
 
protected:
    ~IUnknownCounter() = default;
    IUnknownCounter(const IUnknownCounter&) = delete;
    IUnknownCounter(const&&) = delete;
    IUnknownCounter& operator =(const IUnknownCounter&) = delete;
    IUnknownCounter& operator =(IUnknownCounter&&) = delete;
 
private:
    std::atomic<long> count;
};

4つ目、本日の最後、「データ抽象」です。

これは、virutalメンバ関数による多態性の実現を利用しないclassです。標準ライブラリでも各種STLコンテナやイテレータ、std::basic_string (std::string)、std::threadなど多数存在します。

デストラクタは非virtualでpublicです。継承は原則として用いませんが、Mix-in classを継承することはあります(例:イテレータの実装にboost::iterator_facade (Mix-in)を継承)。

ムーブコンストラクタ・ムーブ代入演算子はできる限りpublicに定義(もちろんdefault定義を推奨)、可能ならコピーコンストラクタ・コピー代入演算子も定義(もちろんdefault定義を推奨)します。

class UserInfo
{
public:
    UserInfo(std::string userName, ptime loginTime);
 
    std::string GetName() const;
    ptime LastLoginTime() const;
 
    UserInfo(UserInfo const&) = default;
    UserInfo(UserInfo&&) = default;
    UserInfo& operator =(UserInfo const&) = default;
    UserInfo& operator =(UserInfo&&) = default;
 
    ~UserInfo() = default;
 
private:
    std::string name;
    ptime lastLogin;
};

繰り返しますが、どの場合でもデストラクタはdefaultで定義します。自分自身では書きません。
どういうことかと言うと、必要な解放処理はできる限りメンバ変数(のデストラクタ)に任せることで、自分(このクラス)自身では「特にデストラクタでやることはない」という状態を目指します。その解放処理のためのクラスだけが唯一デストラクタを明示的に定義して良い例外パターンです。

なお、それすらもstd::shared_ptrやstd::unique_ptrで足りる場合も多いでしょう。unique_ptrについては以下が参考になると思います。

最後に、今日の記事の大元のネタはC++ クラス設計に関するノートです。これを自分なりに解釈して考えをまとめたいという思いで、本記事を書こうと思いました。感謝します。

次ページへ »