Typesafe Enum イディオム

enum を使って定数を定義する場合に,それらの値を使うときは「型名::値」と言う形で記述したいなぁとよく思うのですが,普通の記述 (enum 型名 { ... };) だとこの要求を満たせません.それで,これまで enum を使うときは enum の型名にあたる部分を名前空間に書くことでそれっぽい記述ができるようにしていました.

namespace Fruits {
    enum { Apple, Orange, Lemon, Banana };
}

int main(int argc, char* argv[]) {
    int kind = Fruits::Orange;
    
    switch (kind) {
        case Fruits::Apple:  ...;
        case Fruits::Orange: ...;
        case Fruits::Lemon:  ...;
        case Fruits::Banana: ...;
        default: break;
    }
    
    return 0;
}

ただ,この方法だと受け取る変数の型は int や std::size_t 辺りになるのが難点でした.上記の例で言うと,

Fruits kind = Fruits::Orange;

と言う記述ができるのがベストなのですが,その要求に応える方法が思いつきませんでした.

そんな不満を抱きながら Web を漁っていると「Typesafe Enum イディオム」と言うページが引っかかりました.

template<typename def, typename inner = typename def::type>
class safe_enum : public def
{
    typedef typename def::type type;
    inner val;
 
public:
    safe_enum(type v) : val(v) {}
    inner underlying() const { return val; }
 
    bool operator == (const safe_enum & s) const { return this->val == s.val; }
    bool operator != (const safe_enum & s) const { return this->val != s.val; }
    bool operator <  (const safe_enum & s) const { return this->val <  s.val; }
    bool operator <= (const safe_enum & s) const { return this->val <= s.val; }
    bool operator >  (const safe_enum & s) const { return this->val >  s.val; }
    bool operator >= (const safe_enum & s) const { return this->val >= s.val; }
};
More C++ Idioms/Type Safe Enum

これを使って "enum" を定義すると,最初の要求をかなりの部分まで満たしてくれます.

namespace detail {
    struct Fruits_ {
        enum type { Apple, Orange, Lemon, Banana };
    };
}
typedef safe_enum<detail::Fruits_> Fruits;

int main(int argc, char* argv[]) {
    Fruits kind = Fruits::Orange;
    
    switch (kind.underlying()) {
        case Fruits::Apple:  ...;
        case Fruits::Orange: ...;
        case Fruits::Lemon:  ...;
        case Fruits::Banana: ...;
        default: break;
    }
    
    return 0;
}

「Typesafe Enum イディオム」自体は,

  • enum A 型の値と enum B 型の値を比較しようとするとエラーになるようにしたい
  • enum A 型に存在しない値は作れない (enum A 型の変数に代入できない)ようにしたい

辺りの要求が出発点となっているようです.ただ,それ以外にも「switch 文の case に指定できるようにする」や,今回の私の要求である「型名::値と表記したい」などの要求にも応えるにはどうすれば良いかと,いろいろな人がいろいろな方法を検討している(いた?)ようです.

Hello, Haru Free PDF Library!

C++ での PDF ライブラリだと Haru Free PDF Library の評判が良いようなので,このライブラリの使い方を少しずつメモっていこうと思います.取り合えず,最も基本的なところ(PDF ドキュメント作成からファイルへの保存まで)とテキスト処理まで.

基本形

HPDF_New() で初期化して,必要な処理をいろいろやって HPDF_SaveToFile() でファイルに書き出す,と言うのが基本形となります.

#include <cstdio>
#include <stdexcept>
#include <hpdf.h>

/* ------------------------------------------------------------------------- */
/*
 *  trivial_error_handler
 *
 *  最も簡易的なエラーハンドラー.HPDF_*** から渡された 2 種のエラー番号
 *  をメッセージとして std::runtime_error() を投げる.
 */
/* ------------------------------------------------------------------------- */
void trivial_error_handler(HPDF_STATUS error_no, HPDF_STATUS detail_no, void* user_data) {
    char message[1024] = {};
    sprintf(message, "error: error_no = %04X, detail_no = %d", static_cast<unsigned int>(error_no), static_cast<int>(detail_no));
    throw std::runtime_error(message);
}

/* ------------------------------------------------------------------------- */
//  main
/* ------------------------------------------------------------------------- */
int main(int argc, char* argv[]) {
    HPDF_Doc document = HPDF_New(trivial_error_handler, NULL);
    if (!document) return -1;
    
    int status = 0;
    try {
        /*
         * 日本語の場合.
         * 90ms-RKSJ-H, 90ms-RKSJ-V, 90msp-RKSJ-H, EUC-H, EUC-V が利用可能となる.
         */
        HPDF_UseJPEncodings(document);
        
        /*
         * MS-(P)Mincyo, MS-(P)Gothic が利用可能となる.
         * これらのフォントを使用する場合にはコールする(必要なければ呼ばなくても良い?).
         */
        HPDF_UseJPFonts(document);
        
        /*
         * それ以外のフォントを使用する場合,予めフォントファイルをロードしておく必要がある.
         * ロードしたフォントは,関数から返されたフォント名を用いてアクセスする事になるため,
         * このフォント名をどこかで記憶しておく必要がある.
         */
        
        /*
         * 拡張子が *.ttf のファイルの場合.
         * 第 3 引数はフォントを埋め込むかどうか (HPDF_TRUE or HPDF_FALSE).
         */
        // const char* name = HPDF_LoadTTFontFromFile(document, "folder/filename.ttf", HPDF_TRUE);
        
        /*
         * 拡張子が *.ttc のファイルの場合.
         * *.ttc ファイルには複数のフォントが格納されているため,どのフォントを使用するかを
         * 第 3 引数にインデックスで指定する.
         * 第 4 引数はフォント埋め込みの有無 (HPDF_TRUE or HPDF_FALSE) を指定する.
         */
        // const char* name = HPDF_LoadTTFontFromFile2(document, "folder/filename.ttc", 0, HPDF_TRUE);

        /*
         * メイン (?) 処理.
         * 基本的には,HPDF_AddPage() で新しいページを作成し,
         * 返された HPDF_Page ポインタに対して表示したいコンテンツを追加していくと言う形となる.
         */
        {
            HPDF_Page page = HPDF_AddPage(document);
            // ... do something ...
        }
    
        HPDF_SaveToFile(document, "folder/filename.pdf");
    }
    catch (std::runtime_error& err) {
        std::cerr << err.what() << std::endl;
        status = -1;
    }
    
    HPDF_Free(document);
    return status;
}

テキスト処理

テキスト処理に関しては,PDF ファイルに "BT ... ET" と実際に記述する部分が薄くラップされているだけの形になっているので,PDF ファイルの内部 (stream 内部) 構造を知っていると各関数の役割がイメージしやすくなります.PDF ファイルの構造については,PDF by Hand - Kobu.Com 等を参照下さい.

HPDF_Page page = HPDF_AddPage(document);

/*
 * 使用するフォントをフォント名で取得する.
 * 第 2 引数に指定する文字列は,Courie, Helvetica, MS-Gothic, MS-Mincyo などや
 * HPDF_LoadTTFontFromFile() で返されたフォント名.
 * 第 3 引数にはエンコーディングを指定する.
 * 90ms-RKSJ-H は Shift_JIS (正確には CP932 か) の横書き.
 * ここで,例えば 90ms-RKSJ-V と指定すると,これ以降に出力するテキストは縦書きになる.
 */
HPDF_Font font = HPDF_GetFont(document, "font name", "90ms-RKSJ-H");

/*
 * PDF の (テキスト出力の) フォーマットは,"BT ... ET" と言う形になっている.
 * そのため,テキスト出力開始時に HPDF_Page_BeginText() を
 * テキスト出力終了時に HPDF_Page_EndText() を呼ぶ必要がある.
 */
HPDF_Page_BeginText(page);
{
    /*
     * 出力するテキストのフォントを設定する.
     * 第 2 引数にフォント名,第 3 引数にフォントサイズを指定する.
     */
    double font_size = 10.5;
    HPDF_Page_SetFontAndSize(page, font, font_size);
    
    /*
     * 出力するテキストの色を RGB で設定する.
     * ただし,PDF では RGB 各値を [0,1] で指定することになっている.
     * そのため,白 (#ffffff) は (1, 1, 1), 灰色 (#7f7f7f) は (0.5, 0.5, 0.5) となる.
     */
    HPDF_Page_SetRGBFill(page, 0, 0, 0);
    
    /*
     * 文字列を出力する方法は,大きく分けて 3 通りある.
     *
     * 1. HPDF_Page_TextOut() で出力.
     * HPDF_Page_TextOut() には,(x, y)座標,文字列を指定する.
     * この場合は,ここで指定された座標を始点として表示される.
     * ただし,PDF の座標は左下が原点となる.そのため,多くの場合は (0, page_height)
     * のように,y 座標の指定には注意する必要がある.
     *
     * HPDF_Page_TextOut(page, x, y, description);
     *
     *
     * 2. HPDF_Page_MoveTextPos() で移動しながら HPDF_Page_ShowText() で出力.
     * HPDF_Page_MoveTextPos() は,移動させる数値を現在の座標からの相対座標で指定する.
     * 始点となる座標をいったん決めて,(改行などで) 少しずつ移動させながらテキストを
     * 順次出力していく場合には,これら 2 つの関数を使って出力していく.
     * 尚,この際も PDF の原点に注意する.例えば,横書きで改行しながらテキストを
     * 出力する場合は,以下のようになる.
     *
     * HPDF_Page_MoveTextPos(page, 0, -font_size);
     * HPDF_ShowText(page, description);
     *
     *
     * 3. HPDF_Page_SetTextMatrix() を使用.
     * HPDF_Page_SetTextMatrix() は,拡大/縮小/回転/移動を 3x2 行列で指定するためのものである.
     * 例えば,1. の HPDF_Page_TextOut(page, x, y, description) を行列を用いて表すと
     * 以下のようになる.
     *
     * HPDF_Page_SetTextMatrix(page, 1, 0, 0, 1, x, y);
     * HPDF_ShowText(page, description);
     *
     * その他,簡単な例として,
     *
     * テキストを 2倍に拡大する場合:
     * HPDF_Page_SetTextMatrix(page, 2, 0, 0, 2, x, y);
     *
     * テキストを 60 度回転させる場合:
     * double radian = 60 / 180.0 * 3.141592;
     * HPDF_Page_SetTextMatrix(page, cos(radian), sin(radian), -sin(radian), cos(radian), x, y);
     *
     * 等がある.
     *
     */
    double x = 10.0;
    double y = HPDF_Page_GetHeight(page) - font_size;
    HPDF_Page_SetTextMatrix(page, 1, 0, 0, 1, x, y);
    HPDF_ShowText(page, "description");
     
    /*
     * その他,文字間隔の調整,文字の見た目 (中抜きされた文字等) などテキスト出力には
     * 様々なオプションが存在する.詳細は,
     * http://libharu.sourceforge.net/page_handling.html
     * http://libharu.sourceforge.net/demo/text_demo.c
     * などを参照.
     */
    // HPDF_Page_SetTextRenderingMode (page, HPDF_STROKE);
    // HPDF_Page_SetCharSpace(page, 1.5);
    // HPDF_Page_SetWordSpace(page, 2.5);
}
HPDF_Page_EndText(page);

C++ ラッパ

Haru Free PDF Libray を C++ でラッパしたものを作成しようと思ったら POCO C++ Libraries の sandbox にありました.まだ正式版ではないようなのですが,どんな感じになっているのか一度見てみようと思います.

シェルによるファイルコピー

シェルによるファイルコピーで嵌ったのでメモ.

Win32 API でファイルのコピーを行う方法として,CopyFile() の他にシェルを利用すると言うものがあります.今回 CopyFile() を使用せずにシェルの機能を利用した理由は,「コピーに長い時間を要する際にプログレスバーを表示してくれるから」と言うものでした.

シェルによるコピー自体は,SHFILEOPSTRUCT 構造体に情報を設定して SHFileOperation() を実行するだけなのですが,最初にこれを実行したとき何故かエラーが発生してうまくいきませんでした.何故かなぁと思ってリファレンスを読んでいると,以下の一文を見つけました.

Important You must ensure that the source and destination paths are double-null terminated. A normal string ends in just a single null character. If you pass that value in either the source or destination members, the function will not realize when it has reached the end of the string and will continue to read on in memory until it comes to a random double null value. This can at least lead to a buffer overrun, and possibly the unintended deletion of unrelated data.

// WRONG
LPTSTR pszSource = L"C:\\Windows\\*";

// RIGHT
LPTSTR pszSource = L"C:\\Windows\\*\0";

To account for the two terminating null characters, be sure to create buffers large enough to hold MAX_PATH (which normally includes the single terminating null character) plus 1.

http://msdn.microsoft.com/en-us/library/bb759795.aspx

よく分からないですが,SHFILEOPSTRUCT 構造体の pFrom/pTo に指定する文字列は 2連続 NULL 文字で終端していないとダメだそうです.そんな訳で,シェルによるファイルコピーは以下のような感じになります.

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

bool shell_copy(const std::basic_string<TCHAR>& src, const std::basic_string<TCHAR>& dest) {
    // NOTE: pFrom/pTo に指定する文字列は 2連続 NULL 文字で終端していなければならない.
    std::vector<TCHAR> src_buffer(src.begin(), src.end());
    src_buffer.push_back(0);
    src_buffer.push_back(0);
    
    std::vector<TCHAR> dest_buffer(dest.begin(), dest.end());
    dest_buffer.push_back(0);
    dest_buffer.push_back(0);
    
    SHFILEOPSTRUCT op = {};
    op.hwnd   = NULL;
    op.wFunc  = FO_COPY; // 他に,FO_DELETE, FO_MOVE, FO_RENAME が存在する.
    op.pFrom  = reinterpret_cast<const TCHAR*>(&src_buffer[0]);
    op.pTo    = reinterpret_cast<const TCHAR*>(&dest_buffer[0]);
    op.fFlags = FOF_NOCONFIRMATION | FOF_NOCONFIRMMKDIR | FOF_NOERRORUI; // 必要に応じて変える.
    
    if (SHFileOperation(&op)) return false;
    return true;
}

コントロールのサブクラス化

しばらくブログはお休みモード.今,TreeView を持つアプリを書いているのですが,TreeView のアイテムの名前を変更しようとしたときに,Enter キーを押すと OK ボタンにフォーカスを奪われてしまい意図した動きにならない(「名前の変更」の終了ではなく,ダイアログ自体が終了しています)と言う問題が発生しました.

この問題をどうやって回避するのかなぁと Web を漁っていると,どうやら「コントロールのサブクラス化」と呼ばれる方法で回避するようです.

static WNDPROC DefaultTreeViewEditProc; // サブクラス化する前のデフォルトのウィンドウプロシージャ
static LRESULT CALLBACK TreeViewEditProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    switch (msg) {
    case WM_GETDLGCODE:
        return DLGC_WANTALLKEYS; // Enter, Esc キーなどの処理を行った事を通知
    default:
        break;
    }
    return CallWindowProc(reinterpret_cast<WNDPROC>(DefaultTreeViewEditProc), hWnd, msg, wParam, lParam);
}

static LRESULT CALLBACK DialogProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    switch (msg) {
    case WM_NOTIFY:
    {
        LPNMHDR header = reinterpret_cast<LPNMHDR>(lp);
        if (header->code == TVN_BEGINLABELEDIT) {
            HWND edit = (HWND)SendMessage(tree_view_handle, GWLP_WNDPROC, TVM_GETEDITCONTROL, 0, 0);
            DefaultTreeViewEditProc = (WNDPROC)SetWindowLongPtr(edit, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(&EditProc));
        }
        break;
    }
    default:
        break;
    }
    return FALSE;
}

この方法を知る前は,キーボードのメッセージをフックして無理やり処理するなどしていたのですが,サブクラス化する事でだいぶスマートに処理できるようになりました.

Related Pages

Boost.勉強会#5 名古屋の雑感

Boost.勉強会#5 名古屋に行ってきました.Boost.勉強会が「Boost があるかもしれない勉強会になりつつある」と言う噂もちらほら聞きましたが,非常に濃い内容で楽しかったです.発表された方,関係者の方々いつもお疲れ様です.

以下,印象に残ったものをメモ.発表資料ほか関連リンクは,https://sites.google.com/site/boostjp/study_meeting/study5 にあります.

春のLock-Free祭り (@kumagi, id:kumagi)

意義

マルチスレッドプログラミングに置いて古典的な手法は、共有リソースにアクセスするときはロックをかけることである。ミューテックスセマフォといった排他制御ソースコードのある領域を同時に実行しないようにし、それゆえ、共有メモリの構造を破壊しないようにする。もし、他のスレッドが事前に獲得したロックをあるスレッドが獲得しようとするときは、ロックが解放されるまでスレッドの動作は停止する。

スレッドが停止することは多くの理由で望ましくない。まず、スレッドがブロックされている間は、何も出来ない。そして、スレッドが優先順位の高い処理やリアルタイム処理を行っているならば、そのスレッドを停止することは望ましくない。また、複数のリソースにロックをかけることは、デッドロック、ライブロック、優先順位の逆転を起こすことがある。さらに、ロックを使うには、並列処理の機会を減らす粒度の荒いロックを選択するか、バグを生みやすく注意して設計しないといけない粒度の細かいロックを選択するかというトレードオフ問題を生む。

Lock-freeとWait-freeアルゴリズム - Wikipedia

近年の CPU のマルチコア化にともなって,スレッド間でデータを共有する際に「ロックする事」がボトルネックとして無視できなくなりつつあるようです.そう言った問題に対する解決策として,ロックせずにスレッド間でデータ共有できる(異なるスレッドからアクセスがあってもデータ構造が壊れない)アルゴリズムが提案・研究されているそうです.

発表では,Lock-free アルゴリズムを適用したスタック,キュー,ハッシュマップ辺りに触れられていました.ハッシュマップ辺りからだんだんと付いていけなくなったのですがw,STL 辺りもコレクションクラスの次世代の実装としてこう言った事を考慮されながら行われるようになるのかなぁとか思いながら聞いていました.

Boost.Pythonの有能性 (@fate_fox, fatefox)

C++Python の橋渡しをするためのライブラリ.C++ との橋渡しと言うと SWIG 辺りを思い浮かべるのですが,Python はいち早く Boost に採用されています.その他だと id:moriyoshi さんが Boost.PHP と言うライブラリを実装していたのを思い出しました.

C++ と何かの橋渡しをする経験は自分の場合だと ns-2 のシミュレーションプログラム(C++ と tcl)を書く際に使ったくらいなので,なかなか積極的に利用する場面をイメージできないのですが,ユーザスクリプトが書けるようになると便利になるアプリケーションは数多く存在しているはずなので,できるだけ頑張ってイメージしていければなと思いました.

Boost.statechart / Boost.MSM (@PG_kura, id:kura-replace)

状態遷移を表現するためのライブラリ.これも利用用途をイメージできないでいたのですが,最後の「GUI の動きを状態遷移を用いて表現してテストしやすくする」と言う話に凄く興味を惹かれました.一度真面目に考えてみようと思ったお話.

C++0x総復習 (@wraith13, id:wraith13)

C++0x の FDIS をざーっ紹介してくれたセッション.資料が 160ページを超えてると言う力作で,C++0xチュートリアルとして凄く有用な資料でした.分かりやすく書かれているので,C++er な人は一度目を通してみると良いのではと思います.

  • 発表資料 : revise.cpp0x (PPTX, PDF)

懇親会

懇親会会場にもプロジェクタがあってなかなか濃い展開になっていました.取り合えず名古屋怖いってのは分かりましたzzZ 懇親会でしゃべっていると「ブログ読んでますよ」と言ってもらえた事があって,これは素直に嬉しかったです.このブログは気が向かないと C++ のネタを書かないと言うなんちゃって C++ ブログですが,まぁ末永くお付き合いして頂ければと思いますw

ブログに関しては,ちょっと話題になっていた static おじさんの例みたいに「飯の種(金を生むもの)を無料で公開する奴は馬鹿」みたいな記事をぽつぽつ見かけたりしますが,個人的には何だかんだで「ブログを続けておいて良かった」と思える事には度々遭遇します.「情けは人の為ならず」と言う言葉もあるように,巡り巡って自分にプラスになる事もたくさんあったりするので,そう言った記事にめげずにどんどん情報発信されるような流れになれば良いなぁと考えたりしました.

32bit プロセスは 64bit プロセスを見つけられない

所用で「あるプロセスから別のプロセスを終了させるプログラム」を書いていました.終了させる部分の関数は以下のような感じ.

#include <string>
#include <vector>
#include <windows.h>
#include <tchar.h>
#include <psapi.h>

#pragma comment(lib, "psapi.lib")

namespace process {
    std::size_t kill(const std::basic_string<TCHAR>& name) {
        std::size_t result = 0;
        
        std::vector<DWORD> processes(256, 0);
        DWORD procbytes = 0;
        while (1) {
            if (!::EnumProcesses(reinterpret_cast<DWORD*>(&processes[0]), processes.size() * sizeof(DWORD), &procbytes)) return 0;
            if (procbytes < processes.size() * sizeof(DWORD)) break;
            processes.resize(processes.size() * 2);
            procbytes = 0;
        }
        
        for (std::size_t i = 0; i < procbytes / sizeof(procbytes); ++i) {
            HANDLE process = ::OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ | PROCESS_TERMINATE, FALSE, processes[i]);
            if (!process) continue;
            
            std::vector<HMODULE> modules(256, 0);
            DWORD modulebytes = 0;
            BOOL status = TRUE;
            while (1) {
                status = ::EnumProcessModules(process, reinterpret_cast<HMODULE*>(&modules[0]), modules.size() * sizeof(HMODULE), &modulebytes);
                if (!status || modulebytes < modules.size() * sizeof(HMODULE)) break;
                modules.resize(modules.size() * 2);
                modulebytes = 0;
            }
            
            if (status) {
                for (std::size_t j = 0; j < modulebytes / sizeof(modulebytes); ++j) {
                    TCHAR buffer[2048] = {}; // バッファサイズは適当
                    if (::GetModuleFileNameEx(process, modules[j], buffer, sizeof(buffer) / sizeof(TCHAR)) == 0) continue;
                    std::basic_string<TCHAR> modulename(buffer);
                    std::basic_string<TCHAR>::size_type pos = modulename.find_last_of(_T('\\'));
                    if (pos != std::basic_string<TCHAR>::npos) modulename = modulename.substr(pos + 1);
                    if (modulename == name) {
                        ::TerminateProcess(process, 0);
                        ++result;
                        break;
                    }
                }
            }
            ::CloseHandle(process);
        }
        
        return result;
    }
}

この関数(を実行するプログラム),32bit 版 Windows では問題なく動作するのですが 64bit 版 Windows ではプロセスを終了させてくれませんでした.何故かなぁと原因を調査していると,「終了させる側のプロセスが 32bit なのに対象プロセスが 64bit」だったからのようです.

Yes, you are correct here. From a 32 bit application it is not possible to enumerate the process on 64 bit Windows. So if you want to run your existing code then you need to compile the code as 64 bit.

Basically the reason why Process Explorer is able to enumerate modules of both 32-bit and 64-bit processes at the same time is, when process explorer launches , it also have a 64 bit version with it .

EnumProcessModules() doesn't work on Windows 2008 Server (64 bit)

Windows Vista 以降だと EnumProcessModulesEx と言う関数が用意されており,この引数に 32bit プロセスを対象とするか 64bit プロセスを対象とするかを指定するフラグがあったので,Vista 以降ならいけるかなぁと期待したのですが 32bit → 64bit の場合は無理でした.

This function is intended primarily for 64-bit applications. If the function is called by a 32-bit application running under WOW64, the dwFilterFlag option is ignored and the function provides the same results as the EnumProcessModules function.

EnumProcessModulesEx function (Windows)

そんな訳で,64bit のプロセスを終了させる方法は,FindWindow() を使ってウィンドウのタイトルからハンドルを取得して終了メッセージを送るか,64bit 版のものを作るしかないようです.

ディレクトリ内に存在するファイル数の取得方法の比較

あるディレクトリ内のファイル(+ディレクトリ)数を取得する必要があったのですが,Boost.Filesystem と FindFirstFile とでどの程度の速度差になるのかなと思い調べてみました.まず,Boost.Filesystem 版のファイル数カウント関数.

#include <string>
#include <boost/filesystem.hpp>

std::size_t file_count_boost(const boost::filesystem::path& root) {
    namespace fs = boost::filesystem;
    if (!fs::exists(root) || !fs::is_directory(root)) return 0;
    
    std::size_t result = 0;
    fs::directory_iterator last;
    for (fs::directory_iterator pos(root); pos != last; ++pos) {
        ++result;
        if (fs::is_directory(*pos)) result += file_count_boost(pos->path());
    }
    
    return result;
}

次に FindFirstFile を使ったファイル数カウント関数.

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

std::size_t file_count_native(const std::basic_string<TCHAR>& src) {
    std::basic_string<TCHAR> root = src + _T("\\*.*");
    WIN32_FIND_DATA wfd = {};
    HANDLE handle = FindFirstFile(root.c_str(), &wfd);
    if (handle == INVALID_HANDLE_VALUE) return 0;
    
    std::size_t result = 0;
    do {
        std::basic_string<TCHAR> path(wfd.cFileName);
        if (path != _T(".") && path != _T("..")) {
            ++result;
            if ((wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) result += file_count_native(src + _T('\\') + path);
        }
    } while (FindNextFile(handle, &wfd));
    FindClose(handle);
    
    return result;
}

実行結果

テストは,実行環境にある boost_1_46_1 ディレクトリを指定して調べてみました.実行環境の boost_1_46_1 ディレクトリには,37,834 ファイルと 3,233 ディレクトリが存在するようです.

Boost.Filesystem を使用した場合
$ time ./file-scanning-test.exe
41067

real    0m3.135s
user    0m0.000s
sys     0m0.015s

FindFirstFile を使用した場合
$ time ./file-scanning-test.exe
41067

real    0m0.363s
user    0m0.031s
sys     0m0.000s

実行すると 1桁くらい実行速度が変わっていました.さすがに 1桁も変わるとは思っていなかったので驚きました.やはり変なものを指定される(馬鹿でかいファイルとか)可能性のある部分は,ネイティブ(ここだと Win32 API)で書いておかないとまずい場合があると言う事でしょうか.

Boost.Asio リンク集

id:faith_and_brave さんが最近 Boost.Asio 関連の記事をたくさん投稿してて,どこかにメモっておきたかったのでここにメモ.それ以外のサイトの記事も見つけたら,ここに一緒に追加していこうと思います.

Visutal Studio で Boost C++ Libraries をビルドする

Visutal Studio で Boost C++ Libraries をビルドする必要が出てきたのですが,いくつか嵌った事があったのでメモ.まず,以下のサイトで Windows のバイナリが配布されているようです.バージョンは,3/23 現在で 1.44.0 とやや古い(最新バージョンは 1.46.1)ですが,手軽にインストールするならこれを利用する方法もあります.

今回は,バージョンが古いのと x64 バイナリを作成するためのライブラリも必要だったので自力でビルドする事にしました.

bjam オプション

Boost のビルド自体は,解凍したディレクトリで以下のコマンドを叩けば,基本的には OK です.

C:\code\boost_1_46_1> bootstrap.bat
C:\code\boost_1_46_1> bjam.exe 

ここでは,bjam.exe --help で出てくるもののうち,よく使いそうなものを以下に抜粋しておきます.「--(二重ハイフン)」があったりなかったりするので注意が必要です.特に toolset の箇所は,(ググると)古い情報のものだと --toolset=<value> と指定するようにと言う説明の記事もあるので特に注意する必要があります.










オプション 説明
--stagedir=<value> *.lib がインストールされるディレクトリ.デフォルト値は ./stage*1
--without-<value> ビルドしないライブラリを(その個数分)指定する.ver. 1.46.1 では,以下の 17種類が存在する.

  • date_time
  • filesystem
  • graph
  • graph_parallel
  • iostreams
  • math
  • mpi
  • program_options
  • python
  • random
  • regex
  • serialization
  • signals
  • system
  • test
  • thread
  • wave

デフォルトでは,全てのライブラリがビルドされる.

toolset=<value> ビルドに使用するツールセット(コンパイラ&リンカ?)を指定する.Visual Studio の場合は msvc.尚,msvc-9.0 のようにバージョンを併記すると,(複数の Visual Studio が存在する場合に)使用するツールセットを厳密に指定できる模様.
variant=<value> debug or release のどちらかを指定する.debug,release と記述すると両方のライブラリが同時に生成される?
link=<value> static or shared のどちらかを指定する.Boost のライブラリを静的リンクするかどうかを指定する.
runtime-link=<value> static or shared のどちらかを指定する.こちらは,VC++ の標準ライブラリを静的にリンクするかどうかを指定する.プロジェクトで /MT を指定する場合には runtime-link=static, /MD を指定する場合には runtime-link=shared にしないとライブラリのリンクで問題が発生する模様.

デフォルト(bjam.exe のみ)でビルドすると runtime-link=shared でビルドされるようで,/MT 指定のプロジェクトでビルドしようとすると Boost のライブラリをリンクする際にエラーが発生しました.したがって,/MT でビルドする場合は runtime-link=static を明示的に指定する必要があるようです.

x64 版のビルド

引き続き x64版のビルド.まず,私は x64 版のコマンドラインを出現させるところで躓いたのでその準備から・・・ Express バージョンでは恐らく,x64 用のコマンドプロンプト(のためのショートカット)は作成されないようです.そのため,まずスタートメニューの「Visual Studio コマンドプロンプト」をコピーし,「リンク先」の箇所の引数の部分を 「x86」から「amd64」に変更して x64 用のコマンドプロンプトを作成します.

%comspec% /k ""C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\vcvarsall.bat"" x86
↓
%comspec% /k ""C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\vcvarsall.bat"" amd64

しかし,これだけでは起動に失敗します.amd64 の場合は VC\bin\amd64\vcvars64.bat のバッチファイルを利用するように記述されてあるのですが,該当のファイルが存在しないためです.そこで,以下のような記述をした vcvars64.bat ファイルを作成し,該当ディレクトリにコピーします.尚,これを実行するためには,あらかじめ Windows SDK をインストールしておく必要があります(下記の例だと Windows SDK 7.1).

@echo off
CALL "C:\Program Files\Microsoft SDKs\Windows\v7.1\Bin\SetEnv.cmd" /x64

この後,表示されたコマンドプロンプト上で以下のように address-model=64 を付けてビルドすると,x64 用のライブラリがビルドされるようです.

C:\code\boost_1_46_1> bjam.exe toolset=msvc address-model=64 link=static runtime-link=static

最初に x64用のコマンドプロンプトを起動しないと toolset=msvc-10.0.amd64 とやっても address-model=64 とやっても x86用のライブラリが作成されてしまうようで,結構長い時間ここで嵌りました.

*1:正確には,指定したディレクトリ下に lib ディレクトリが作成され,その中に *.lib がインストールされる.

継承とテンプレート

この記事は,C++プログラマであるかを見分ける10の質問 - Life like a clown の「抽象クラスとテンプレートクラスの使い分けについてインターフェースと言う観点から述べよ」に対する回答的な記事です.

インターフェース的な基底クラスは,テンプレートで代替できる場合があります.例えば,

class interface {
public:
    virtual ~interface() {}
    virtual void do_something() = 0;
};

void func(interface& x) {
    x.do_something();
}

と言うインターフェース(的な)クラスを引数に取る関数 func() は,以下のように書く事でも実現できます.

template <class T>
void func(T& x) {
    x.do_something();
}

さて.この 2種類をどのように使い分けるかについては私自身もはっきりとした答えを持っていませんが,個人的には「可能ならばできるだけテンプレートの形で書く」と言う方針を取っています.その理由は,ユーザに class implemented : public interface と書かせるのがめんどくさいと言う切実な理由もありますが,その他にキャストは可能な限り発生させたくないと言うものがあります.virtual 関連の記事(overload と override と hiding - Life like a clown, virtual デストラクタ - Life like a clown)でも述べましたが,基底クラスのポインタから派生クラスのメンバ関数(メソッド)を実行する場合 virtual の有無で挙動が変わる事があり,プログラマの不注意などで予期せぬ不都合を発生させる事があります.そのため,アップキャストであっても発生させなくて良いのであればできるだけ発生させないと言うのが個人的な方針です.

継承とテンプレートの使用方法に関する別の比較例としては,インターフェース的なものを定義したときのその実装の仕方があります.例えば,上記の Interface を実装する場合,継承と言う手段を用いる場合は以下のようなコードになります.

class implemented : public interface {
public:
    virtual ~implemented() {}
    
    virtual void do_something() {
        std::cout << "implement do_something()" << std::endl;
    }
};

int main() {
    implemented x;
    x.do_something();
    return 0;
}

これをテンプレートを用いて代替する場合,以下のような書き方が考えられます.

class policy_implemented {
public:
    void exec() {
        std::cout << "implement do_something()" << std::endl;
    }
};

template <class DoSomethingPolicy>
class implemented {
    DoSomethingPolicy policy_;
public:
    explicit implemented(const DoSomethingPolicy& policy = DoSomethingPolicy()) :
        policy_(policy) {}
    
    void do_something() {
        policy_.exec();
    }
};

int main() {
    implemented<policy_implemented> x;
    x.do_something();
    return 0;
}

継承する代わりに,インターフェース的なクラス(のインスタンス)をメンバ変数に持たせて実際の処理をそのクラスに移譲すると言う戦略が考えられます.これは,デザインパターンで言うところの TemplateMethodStrategy の対比と考える事ができます.したがって,使い分けに関しても「TemplateMethod と Strategy をどう使い分けるか」と言う観点で考える事もできます.

継承とテンプレートの合わせ技

インターフェース的な使い方をする場合,継承とテンプレートとで代替可能な場面が多々あるのですが,中には(普通にやると)継承じゃないと(正確には,きちんとインターフェース的なクラスを定義していなければ)実現できないケースもあります.この際に,ユーザからこの継承部分を隠蔽する方法が提案されました.この方法は Type Erasure と呼ばれています.

非テンプレート基本クラスとテンプレート派生クラスによる Type Erasure では,その名のとおり,リスト 7-15 のような非テンプレート基本クラス (base) とテンプレート派生クラス (derived) を用意しておきます.

// リスト 7-15
class base {
public:
    virtual ~base() {}
};

template <class T>
class derived : public base {
    T value_;
public:
    derived(const T& value) : value_(value) {}
    virtual ~derived() {}
};

これを用意しておくと,継承の特性を利用して derived が持つ型情報 T を base に隠すことができ,メンバ関数テンプレートで受け取ったパラメータをメンバとして保持できます(リスト 7-16).

// リスト 7-16
class X {
    base* p_;
public:
    template <class T>
    X(const T& value) {
        p_ = new derived<T>(value);
    }
    
    ~X() { delete p_; }
};
C++テンプレートテクニック

Type Erasure については,静的型付け言語におけるダックタイピング (Type Erasure) - Life like a clownhttp://d.hatena.ne.jp/tt_clown/20101212/1292132302 辺りでも触れていますので,参照下さい.