Web 上での C++11 関連記事まとめ

これは,C++11 Advent Calendar 2011 参加記事です.現在,開催されている Advent Calendar の一覧は Advent Calendar 2011 (jp) 開催予定リスト - Life like a clown を参照下さい.

C++11 (C++0x) に関しての情報は 本の虫Faith and Brave - C++で遊ぼう でかなり詳細な記述がありますので,情報を探す場合は site:cpplover.blogspot.comsite:d.hatena.ne.jp/faith_and_brave 辺りを指定してググってみると良いかもしれません.この記事でも,該当ブログの記事をメインにリンク集を作成しています.

尚,自分の理解が追い付いてないものや見逃したもの等があるので,結構な抜けがあるかと思います.

型推論・型指定子 (auto, decltype)

auto は右辺値の型を推論してくれる機能,decltype は式の演算結果の型を取得する機能となります.

初期化構文 (initializer_list)

std::vector 等の STL コンテナの初期化が,一つずつ push_back などのようにしなくとも {} (波括弧) で一括して行えるようになる機能です.これに合わせて,全ての初期化が {} (波括弧) で統一できるようにもなるようです.尚,リスト初期化は同じ型しか渡せないという制限があるようで,任意の数の違う型の引数を指定する場合は Variadic Templates を代わりに使う事になるようです.

ラムダ式

匿名関数(無名関数)と呼ばれるもの.昔,一番最初に聞いた時は「は?何それ?」と言う感想を抱いたのですが,他プログラミング言語を見ても随分とポピュラーな機能になってきた感があります.

rvalue reference, および Move Semantics

C++03 での問題点の一つとしてよく挙げられていた「コピーされすぎ(コピーコンストラクタが実行されすぎ)」を緩和する事を目的の一つとして導入された機能です.名称等の問題もあって馴染みにくい機能ですが,第 3 者にライブラリを提供する立場にある開発者であれば,これから必須となって来る機能だろうと思います.

定数式 (constexpr)

コンパイル時に実行される関数を定義できるようになると言うもの.constexpr 指定のものであれば swtich 文の case にも使用できるようになりので,これまでマクロで行われていた部分が代替可能になる可能性があります.ただし,記述する関数本体に厳しい制限があるので,実装するのが大変になる場合があるかもしれません.

ライブラリ

C++11 で新規に導入されるライブラリ群に関するリンク.C++11 で新規に導入されたライブラリの多くは Boost C++ Libraries から採用されたものであるため,Boost C++ Libraries の該当ライブラリの説明も参考になります.また,いくつかのものは TR1 (Technical Report 1) として,コンパイラによっては既に実装されている事もあります.

文字列・数値の相互変換

boost::lexical_cast() を用いて行っていた文字列・数値の相互変換関数群.

日付・時間

C++0x から Boost へ逆輸入された珍しい例.そのため,Boost には Boost.DateTime と Boost.Chrono と言う 2 種類の日付・時間を扱うライブラリが存在しています.

参照ラッパ
メンバ関数アダプタ (mem_fn)
エラー情報のサポート (system_error)

その他

単体の章を設けるかその他のサブ項目にするかについては,完全に私の主観で行ってますので,いろいろあるかとは思いますがまぁ・・・反応を見ながら移した方がいいかなと判断した場合は変更するかもしれません.

NULL ポインタ (nullptr)

NULL が意図せず int 型に推論されないようにするための修正.

final, および override 指定
テンプレートの別名付け (Template Aliases)

template クラスの typedef.実際には typedef ではなく using と言うキーワードを使うようです.

例外を送出しないことの明示 (noexcept)

C++03 までは,デストラクタ等によく throw() と書いていた部分に相当するものです.C++11 で例外指定が deprecated になったので,その代わりと言う感じでしょうか.

コンパイル時 assert (static_assert)

assert() のコンパイル時版.BOOST_STATIC_ASSERT() と言うマクロがありましたが,これが言語機能としてサポートされたと言う感じでしょうか.

関数の戻り値の後置

decltype との併用で便利な場面が出てくると言った感じでしょうか.

コンパイラが生成する関数への default/delete 指定

コンパイラによって自動生成されるコンストラクタ,デストラクタ,代入演算子等の各種関数がある程度制御できるようになります.

明示的な型変換

キャスト演算子とも言われていますが,これに explicit キーワードを付けられるようになるようです.explicit キーワードの付いた型変換用演算子を使用する場合は,(Type)x のように変換後の型を明示的に記述する必要があります.

委譲コンストラクタ

あるコンストラクタから,同じクラスの別のコンストラクタが利用できるようになるようです.

メンバ変数の初期値の指定

C# 辺りで馴染みのあるメンバ変数の定義部分での初期化が可能になるようです.

Boost.GIL で HSV 変換を行う

これは,Boost Advent Calendar 2011 参加記事です.現在,開催されている Advent Calendar の一覧は Advent Calendar 2011 (jp) 開催予定リスト - Life like a clown を参照下さい.

当初,Boost.GIL の(初歩的な)逆引きリファレンス的な記事にしようかと思ったのですが,Boost.勉強会の Boost.GIL 画像処理入門 (PDF) に大体の事が書かれてあってやる事がなかったので,後半に書く予定だった「画像を明るくする」,「画像を鮮やかにする」等の処理を行うための HSV 変換についてをメインに書くことにします.

HSV 変換のアルゴリズムについては以下を参照下さい.

これを Boost.GIL で実現する方法ですが,boost::gil::transform_pixel と言う関数を使うと良いようです.

boost::gil::transform_pixel(const View& src, const View& dst, F functor)

対象のピクセルを受け取って別のピクセルを返す関数を、全ピクセルに対して適用します。この関数は、画素値と出力値が1対1対応である場合に用います(e.g. 明度、コントラスト, レベル補正, トーンカーブ, etc...)。

boost::gil::transform_pixel_positions(const View& src, const View& dst, F functor)

周囲のピクセルの値を元に結果となるピクセルを返す関数を、全ピクセルに対して適用します。transform_pixelとは、受け取る引数がロケータであるという点が異なっており、周囲の画素値を元に実際の出力値を求める場合に有効です(e.g. Blur, Sovel, Laplacian, etc...)。

映像奮闘記: boost.GILで組む、ジェネリックな画像処理のアルゴリズム

尚,第 3 引数に指定する関数オブジェクトは以下のようになります.

class transform_functor {
public:
    template<typename PixelT>
    PixelT operator()(PixelT src);
};

HSV 色空間内で処理を行うための hsvfilter

今回は,この形に沿った関数オブジェクトで「RGB 値を HSV 値に変換してユーザに何らかの処理を行ってもらった後,再び RGB 値に戻す」と言う機能を持つ hsvfilter を実装してみます.尚,現時点では各ピクセルが RGB 値のみを持つ画像でないとエラーになります.それ以外のピクセル型については,そのうち調べて実装していこうかと思います.

#ifndef HSVFILTER_H
#define HSVFILTER_H

#include <boost/tuple/tuple.hpp>
#include <boost/tuple/tuple_io.hpp>
#include <boost/function.hpp>
#include <boost/gil/gil_all.hpp>
#include <cassert>

/* ------------------------------------------------------------------------- */
/*
 *  hsvfilter
 *
 *  各ピクセルを HSV 値に変換した上で何らかの処理を行うためのフィルタ.
 */
/* ------------------------------------------------------------------------- */
class hsvfilter {
public:
    typedef boost::function<void (int&, int&, int&)> predicate_type;
private:
    typedef boost::tuple<int, int, int> rgb_type;
    typedef boost::tuple<int, int, int> hsv_type;
public:
    /* --------------------------------------------------------------------- */
    //  constructor
    /* --------------------------------------------------------------------- */
    explicit hsvfilter(predicate_type pred) : pred_(pred) {}
    
    /* --------------------------------------------------------------------- */
    //  operator()
    /* --------------------------------------------------------------------- */
    template <class PixelT>
    PixelT operator()(PixelT pixel) {
        rgb_type src = this->to_rgb(pixel);
        hsv_type hsv = this->rgb2hsv(src);
        pred_(hsv.get<0>(), hsv.get<1>(), hsv.get<2>());
        rgb_type dest = this->hsv2rgb(hsv);
        return this->from_rgb<PixelT>(dest);
    }

private:
    /* --------------------------------------------------------------------- */
    /*
     *  to_rgb
     *
     *  Boost.GIL の各種ピクセル型から RGB へ変換する.
     *  TODO: 現在は RGB の値のみが格納されてあるものしかサポートされて
     *  いない.その他のピクセル型を調査して対応する.
     */
    /* --------------------------------------------------------------------- */
    template <class PixelT>
    rgb_type to_rgb(const PixelT& pixel) {
        assert(boost::gil::num_channels<PixelT>::value == 3);
        return boost::make_tuple(pixel[0], pixel[1], pixel[2]);
    }
    
    /* --------------------------------------------------------------------- */
    /*
     *  from_rgb
     *
     *  RGB から Boost.GIL の各種ピクセル型へ変換する.
     *  TODO: 現在は RGB の値のみが格納されてあるものしかサポートされて
     *  いない.その他のピクセル型を調査して対応する.
     */
    /* --------------------------------------------------------------------- */
    template <class PixelT>
    PixelT from_rgb(const rgb_type& rgb) {
        assert(boost::gil::num_channels<PixelT>::value == 3);
        
        PixelT dest;
        dest[0] = rgb.get<0>();
        dest[1] = rgb.get<1>();
        dest[2] = rgb.get<2>();
        return dest;
    }
    
    /* --------------------------------------------------------------------- */
    /*
     *  rgb2hsv
     *
     *  RGB 値を HSV 値に変換する.
     */
    /* --------------------------------------------------------------------- */
    hsv_type rgb2hsv(const rgb_type& rgb) {
        const int R = std::min(rgb.get<0>(), 255);
        const int G = std::min(rgb.get<1>(), 255);
        const int B = std::min(rgb.get<2>(), 255);
        
        const int maximum = std::max(std::max(R, G), B);
        const int minimum = std::min(std::min(R, G), B);
        
        const double V = maximum;
        const double S = (maximum != 0) ? 255.0 * (maximum - minimum) / static_cast<double>(maximum) : 0.0;
        
        double H = 0.0;
        if (S > 0.0 && maximum != minimum) {
            if (maximum == R) H = 60.0 * (G - B) / static_cast<double>(maximum - minimum);
            else if (maximum == G) H = 60.0 * (B - R) / static_cast<double>(maximum - minimum) + 120.0;
            else H = 60.0 * (R - G) / static_cast<double>(maximum - minimum) + 240.0;
            if (H < 0.0) H += 360.0;
        }
        
        return boost::make_tuple(static_cast<int>(H), static_cast<int>(S), static_cast<int>(V));
    }
    
    /* --------------------------------------------------------------------- */
    /*
     *  hsv2rgb
     *
     *  HSV 値を RGB 値に変換する.
     */
    /* --------------------------------------------------------------------- */
    rgb_type hsv2rgb(const hsv_type& hsv) {
        const int H = hsv.get<0>();
        const int S = hsv.get<1>();
        const int V = hsv.get<2>();
        
        if (S == 0) return boost::make_tuple(V, V, V);
        
        const int Hi = H / 60;
        const double F = H / 60.0 - Hi;
        const double M = (1.0 - S / 255.0) * V;
        const double N = (1.0 - S / 255.0 * F) * V;
        const double K = (1.0 - S / 255.0 * (1.0 - F)) * V;
        
        switch (Hi) {
            case 0: return boost::make_tuple(V, K, M);
            case 1: return boost::make_tuple(N, V, M);
            case 2: return boost::make_tuple(M, V, K);
            case 3: return boost::make_tuple(M, N, V);
            case 4: return boost::make_tuple(K, M, V);
            case 5: return boost::make_tuple(V, M, N);
            default: break;
        }
        
        return boost::make_tuple(-1, -1, -1);
    }
    
private:
    predicate_type pred_;
};

#endif // HSVFILTER_H

サンプルプログラム

この hsvfilter を使用して,画像を明るくするサンプルプログラムは以下になります.ここでは画像の明るさを調整していますが,これが例えば,画像の鮮やかさを調整する場合は,value の代わりに saturation の値を弄る事になります.

/* ------------------------------------------------------------------------- */
/*
 *  brightener
 *
 *  各ピクセルの明るさを一律に明るくしていくクラス.
 */
/* ------------------------------------------------------------------------- */
class brightener {
public:
    explicit brightener(int brightness) :
        brightness_(brightness) {}
    
    void operator()(int& hue, int& saturation, int& value) {
        value += brightness_;
        if (value > 255) value = 255;
    }
    
private:
    int brightness_;
};

/* ------------------------------------------------------------------------- */
//  main
/* ------------------------------------------------------------------------- */
int main(int argc, char* argv[]) {
    if (argc < 3) return -1;
    
    std::string src_path = argv[1];
    std::string dest_path = argv[2];
    
    boost::gil::rgb8_image_t src_image;
    boost::gil::jpeg_read_image(src_path, src_image);
    boost::gil::rgb8_image_t dest_image(src_image.dimensions());
    
    // メイン処理
    int brightness = 50;
    boost::gil::transform_pixels(
        boost::gil::const_view(src_image),
        boost::gil::view(dest_image),
        hsvfilter(brightener(brightness))
    );
    
    boost::gil::jpeg_write_view(dest_path, boost::gil::view(dest_image));
    
    return 0;
}


右クリックによるドラッグ&ドロップ時のドロップ先のパスを取得する

右クリックでファイルやフォルダをマウスでドラッグ&ドロップすると,「ここにコピー」や「ここに移動」のようなコンテキストメニューが表示されます.

このメニューで「ここに〜」を選択すると,マウスでドロップしたフォルダへ該当の処理(コピー,移動,...)が行われる訳ですが,「ドロップ先のパス」はどうやって取得しているのか分からなかったので調べてみました.

HRESULT Initialize(
  [in]  PCIDLIST_ABSOLUTE pidlFolder,
  [in]  IDataObject *pdtobj,
  [in]  HKEY hkeyProgID
);

・・・(中略)・・・
A pointer to an ITEMIDLIST structure that uniquely identifies a folder. For property sheet extensions, this parameter is NULL. For shortcut menu extensions, it is the item identifier list for the folder that contains the item whose shortcut menu is being displayed. For nondefault drag-and-drop menu extensions, this parameter specifies the target folder.

IShellExtInit::Initialize method (Windows)

右クリックによるドラッグ&ドロップの場合,IShellExtInit::Initialize の引数 pidFolder にドロップ先の PID? が設定されているようなので,この関数を実装する際に PID から該当パスを記憶しておくようです.

STDMETHODIMP Initialize(LPCITEMIDLIST pIDFolder, LPDATAOBJECT pDataObj, HKEY hKeyID) {
    // ...
    
    if(pIDFolder) {
        this->IsDragAndDrop_ = true;
        TCHAR buffer[MAX_PATH] = {};
        ::SHGetPathFromIDList(pIDFolder, buffer);
        this->DropPath_ = buffer;
    }
    else this->IsDragAndDrop_ = false;
    
    // ...
}

_tcscpy_s (strcpy_s, wcscpy_s) の引数

深いディレクトリの削除を再帰的に行う。(ReadOnlyの場合も対応) を使おうとして気づいた事.cstring の文字列処理関数を使おうとすると CRT のセキュリティ機能 関係の警告がよく出てきます.めんどくさいから警告を抑止しようかと思ったのですが,ふと strcpy_s の引数を見ると面白い事に気づきました.

template <size_t size>
errno_t strcpy_s(
   char (&strDestination)[size],
   const char *strSource 
); // C++ only

template <size_t size>
errno_t wcscpy_s(
   wchar_t (&strDestination)[size],
   const wchar_t *strSource 
); // C++ only

template <size_t size>
errno_t _mbscpy_s(
   unsigned char (&strDestination)[size],
   const unsigned char *strSource 
); // C++ only
strcpy_s、wcscpy_s、_mbscpy_s

C++ の場合の strcpy_s 系関数はテンプレートを使用しているものも用意しているようで,第1引数に配列を指定する限りは,ユーザ(呼び出し側)が引数で明示的に要素数を指定しなくても良いようになっているようです.なので,多くの場合は呼び出し側は引数を変更する必要はなく,関数名に _s を付けるだけで良いようになっている模様.

追記: _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES

闇の軍団的には_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES はどうなんだろう? RT @tt_clown: _tcscpy_s (strcpy_s, wcscpy_s) の引数 - http://t.co/BYzdZfng

http://twitter.com/#!/TKinugasa/status/121107078864584704

_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES なんてオプションがあったんですね.

テンプレート オーバーロードを使用することもできます。 _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES を 1 に定義すると、セキュリティが強化されたバリアントを自動的に呼び出す、標準 CRT 関数のテンプレート オーバーロードが有効になります。_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES が 1 の場合、コードを変更する必要はありません。 内部では、strcpy の呼び出しが strcpy_s の呼び出しに変換され、サイズ引数が自動的に指定されます。

#define _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES 1

...

char szBuf[10];

strcpy(szBuf, "test"); // ==> strcpy_s(szBuf, 10, "test")

_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES は、strncpy などのカウントを取る関数には影響しません。 カウント関数に対するテンプレート オーバーロードを有効にするには、_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES_COUNT を 1 に定義します。 ただし、定義する前に、コードがバッファー サイズではなく (よくある間違い)、文字数を渡しているかどうかを確認しておく必要があります。 また、セキュリティが強化されたバリアントを呼び出す場合、関数の呼び出し後、バッファーの最後に明示的に null 終端文字を書き込むコードが必要です。 切り捨て動作が必要な場合については、_TRUNCATE の説明を参照してください。

セキュリティ保護されたテンプレート オーバーロード

コンパイラとの互換性を考えると _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES を使うのが一番良いのかなと言う気はします.

GetLastError() に対応するメッセージを取得する

すぐ忘れるのでメモ.GetLastError() でエラー ID を取得して,FormatMessage() でそれに対応するメッセージを取得する関数

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

/* ------------------------------------------------------------------------- */
/*
 *  GetErrorMessage
 *
 *  指定されたエラー ID に対応するメッセージを取得する.
 *  http://msdn.microsoft.com/ja-jp/library/cc428939.aspx
 */
/* ------------------------------------------------------------------------- */
std::basic_string<TCHAR> GetErrorMessage(DWORD id) {
    std::basic_string<TCHAR> dest;

    LPVOID buffer = NULL;
    DWORD result = FormatMessage(
        FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
	NULL,
        id,
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
        reinterpret_cast<TCHAR*>(&buffer),
        0,
        NULL
    );
    	
    if (result > 0 && buffer != NULL) {
        dest = reinterpret_cast<TCHAR*>(buffer);
        LocalFree(buffer);
        
        // 末尾の改行を除去
        if (!dest.empty() && dest[dest.size() - 1] == _T('\n')) dest.erase(dest.size() - 1);
        if (!dest.empty() && dest[dest.size() - 1] == _T('\r')) dest.erase(dest.size() - 1);
    }
    return dest;
}

/* ------------------------------------------------------------------------- */
/*
 *  GetLastErrorMessage
 *
 *  GetLastError() に対応するメッセージを取得する.
 */
/* ------------------------------------------------------------------------- */
std::basic_string<TCHAR> GetLastErrorMessage() {
    return GetErrorMessage(GetLastError());
}

テキストボックスのテキストを取得

テキストボックスのテキストの取得方法のメモ.

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

std::basic_string<TCHAR> GetText(HWND handle) {
    int length = ::GetWindowTextLength(handle);
    if (length <= 0) return std::basic_string<TCHAR>();
    
    std::vector<TCHAR> buffer(length + 1, 0);
    int result = ::GetWindowText(handle, reinterpret_cast<TCHAR*>(&buffer[0]), buffer.size());
    if (result <= 0) return std::basic_string<TCHAR>();
    
    // NULL 文字も含まれているなら以下でも良い?
    // return std::basic_string<TCHAR>(reinterpret_cast<TCHAR*>(&buffer[0]))
    std::vector<TCHAR>::iterator pos = buffer.begin();
    std::advance(pos, result);
    return std::basic_string<TCHAR>(buffer.begin(), pos);
}

バッファサイズをどれだけにするかをどうやって決めるんだろうと思っていたら,GetWindowTextLength() で先にテキスト長だけ取得して決めるようです.可変長配列を用意する場合,ややトリッキー?な記述になりますが std::vector を利用するのが最も楽かなと思います.

Trivial logger for VC++

ログ出力をどうしようかと google-glog を触ってみたのですが何か上手くいかず,結局,自作で逃げてしまいました.そんな訳で簡易リファレンス代わり.ログ出力関連は Boost.Log が採択されているようなので,正式に Boost 入りしたらまた考えようと思います.

Examples

使い方としては,Logger::Configuration() で初期設定をした後,各種マクロでログを出力していく形.

#include <windows.h>
#include <tchar.h>
#include <psdotnet/logger.h>
#include <psdotnet/appender.h>

int _tmain(int argc, TCHAR* argv[]) {
#ifndef PSDOTNET_INVALIDATE_LOG_MACRO
    PsdotNet::FileAppender fapp(_T("foo/bar/bas.log"));
    PsdotNet::Logger::Configuration(fapp, PsdotNet::LogLevel::Warn);
#endif
    
    LOG_TRACE(_T("trace macro"));
    LOG_DEBUG(_T("debug macro"));
    LOG_INFO(_T("info macro"));
    LOG_WARN(_T("warn macro"));
    LOG_ERROR(_T("error macro"));
    LOG_FATAL(_T("fatal macro"));
    
    return 0;
}

LogLevel

定義している LogLevel は,Quiet, Trace, Debug, Info, Warn, Error, Fatal の 7 種類.Quiet はまったくログを出力したくない時に指定する定数で,他の値はまぁよく見かけるものを定義しています.Configuration() 時に Info を指定すると LOG_TRACE, LOG_DEBUG のマクロで記述した部分は出力されないとか,そんな感じ.

IAppender

PsdotNet::Logger では IAppender がほとんど仕事をする事になっていて,ここはユーザ定義に任せています.IAppender は,Log4J の Appender と Layout を併せたような役割となります.既に定義してあるのは,指定されたファイルに出力していく FileAppender と,全く何もしない(渡されたログ情報を全て捨てていく) NullAppender の 2 種類になります.

class LogInfo;
class IAppender {
public:
    typedef std::size_t size_type;
    typedef TCHAR char_type;
    typedef std::basic_string<TCHAR> string_type;

    IAppender() {}
    virtual ~IAppender() {}

    virtual void Set(const LogInfo& info) = 0;
    virtual void operator()(const char_type* format, ...) = 0;
    virtual bool Reset() { return true; }
    virtual bool Finish() { return true; }
};

FileAppender のコンストラクタでの第 2 引数に指定できるオプションは現状では以下の通り.オプションは,"|" (OR) で複数指定する事ができます.

  • AppendMode ..... 末尾に追加モードでファイルを開く.
  • FlushOnWrite ..... 書き込む度にフラッシュする.
  • CloseOnWrite ..... 書き込む度にファイルを閉じる.
  • WriteDateTime ..... 日時を出力する.
  • WriteLevel ..... ログレベルを出力する.
  • WriteFileAndLine ..... ファイル名,行番号を出力する.
  • WriteAll ..... 日時,ログレベル,ファイル名,行番号を出力する (WriteDateTime | WriteLevel | WriteFileAndLine と同等).

ログ出力を無効にする方法

ログを出力させないようにする方法は,以下の 3 通りがあります.

  • PsdotNet::Logger::Configuration() を実行しない.
  • PsdotNet::Logger::Configuration() で PsdotNet::LogLevel::Quiet を指定する.
  • psdotnet/logger.h をインクルードする前に PSDOTNET_INVALIDATE_LOG_MACRO を定義する.

このうち上 2 種類に関しては,LOG_XXX() マクロで渡された文字列を NullAppender に渡して捨てる事でログを出力しないようにしています.すなわち,ログ出力はされませんが関数の実行自体は行われています.これに対して,PSDOTNET_INVALIDATE_LOG_MACRO を定義した場合は LOG_XXX() マクロが全てプリプロセス時点で消去されます.このため,関数の実行自体も行われません.

C++0x 全会一致で承認され C++11 へ

C++0xの最終国際投票はこの水曜日に終わり、さきほど投票結果が届いた… “全会一致で承認する”

我々が”C++0x”と呼んできたC++の次期改正案はついに国際標準になった! ジュネーヴのISOから発行されるのはもう数ヶ月先になるだろうが、それでも今年中には発行されるだろう。ついに大手を振って”C++11”と呼べるようになった。

http://zakkas783.tumblr.com/post/8838927425/c-0x

そんな訳でようやく次期 C++ 改正案である C++0x の規格が正式に承認されたようです.C++0x と言う名前からも分かるように,当初 200X 年のどこかで確定されるはずだった次期規格ですが,デッドラインである 2009 年を余裕で超え,一時は「0x は 16進数と言う意味だったんだよ!」みたいなネタで語られるようにもなった C++0x さん.このたびの承認にともなって、晴れて C++11 と言う名称に変更?になるようです.

実装としては,gcc がかなり頑張ってるらしいですが(VC++ さんは相変わらず散々な言われようのようです)まだまだ実装を完全に完了しているコンパイラは存在しないので(規格が確定していなかったので当たり前ですが・・・),どの辺りから仕事とかでも使われるようになるのかはまだ不透明な感じと言ったところでしょうか.

内容については私自身も大雑把にしか把握していないのですが,auto (型推論) や新しい初期化リスト辺りはほとんどの C++ プログラマにとってもコーディングのストレスを軽減してくれるものになってくれるでしょうし,rvalue (ムーブコンストラクタ) は不要なコピーを抑えるために(場合によっては)必須となってくるでしょうしと,ぼちぼちきちんと把握していかなければなぁと感じるところではあります.

C との互換性のために相変わらずヤバイ (C 的な) 書き方をするとすぐにメモリリーク等を起こしてしまったり,ものによっては「何でそんな挙動になるんだよ><」とか思ったり,エラーメッセージ見て「日本語でおk」と思ったりする事もある「難しい」 C++ さんですが,C++11 でだいぶ改善されるのではと思います.Perl 辺りも「モダンな書き方」の啓蒙でかなり改善された部分はあるようなので,そう言ったモダンな書き方が広まって「よく分からないけど C++ は難しい」みたいな認識が少しでも薄まると良いなと思います.

ウィンドウプロシージャをどうラップするか?

まだメモ書き段階.

Win32 APIGUI プログラミングをする場合,WndProc と呼ばれるコールバック関数に処理が集中してしまうため,注意しておかないとこの関数がカオスになります.この問題への対策として最初に思い浮かぶのが,WM_XXX 毎に関数を分けると言う方法で,これに関してはウィンドウメッセージクラッカーと言うマクロ群が Microsoft から提供されているようです.

例えば上記のコードは、メッセージクラッカを使うと次のようにかけます。

LRESULT CALLBACK WindowProc ( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) {
    switch ( uMsg ) {
    HANDLE_MSG (hwnd, WM_CREATE, OnCreate);
    HANDLE_MSG (hwnd, WM_COMMAND, OnCommand);
    }
    return DefWindowProc (hwnd, uMsg, wParam, lParam);
}

BOOL Cls_OnCreate (HWND hwnd, LPCREATESTRUCT lpCreateStruct) {
    // WM_CREATE の処理
    return TRUE;
}

void Cls_OnCommand (HWND hwnd, int id, HWND hwndCtl, UINT codeNotify) {
    // WM_CREATE の処理
}

注目するところは、(1) WindowProc の switch 文が HANDLE_MSG の並びに変わっているところ、もうひとつはそれぞれのメッセージ (ここでは WM_CREATE と WM_COMMAND) の処理がそれぞれ異なるプロシージャで行われていることです。

メッセージクラッカ - Web/DB プログラミング徹底解説

これである程度は関数を分離する事ができる訳ですが,何らかの状態を保持する必要が出てきた場合に苦しくなります.状態の保持に関しては,Win32 API だとウィンドウ生成時に LPARAM と言う引数に任意のデータを渡せるので,この引数に適当な構造体なりクラス(へのポインタ)を指定して,そこに状態を保存すると言う方法がよく取られるようです.

ただし,この方法を用いた場合,WM_CREATE (もしくは WM_INITDIALOG) で渡された任意のユーザデータをどこかに記憶しておく必要があり,何も考えずにやると関数内の static 変数に記憶すると言う形になってしまいます.モーダルダイアログ等,「同時には一つのウィンドウしか開かない」と言う前提があるようなケースではこれでも良いのですが,そうではない場合には問題になってきます.

この問題への回避策としては,HWND とユーザデータ (構造体やクラスへのポインタ) のマップを保持できる static 変数を関数内に持って,そのマップへ適切なタイミングで追加/削除を行う事で回避すると言うパターンが多いようです.

これをもう少し推し進めて,1つのゲートウェイ的な (static な) WndProc 関数と,インスタンス毎の (static ではない) WndProc を用意して,ユーザ(プログラマ)から見ると WndProc が自分のウィンドウクラスの通常のメンバ関数の一つであるように振舞わせると,記述が楽に書けるようになると予想されます(下記の例はモーダルダイアログの場合).

class CommonDialog {
public:
    virtual ~CommonDialog() {}
    
    /* --------------------------------------------------------------------- */
    //  ShowDialog
    /* --------------------------------------------------------------------- */
    int ShowDialog(HWND owner, const TCHAR* resource_name) {
        return ::DialogBoxParam(::GetModuleHandle(NULL), resource_name, owner, CommonDialog::StaticWndProc, reinterpret_cast<LPARAM>(this));
    }
    
    /* --------------------------------------------------------------------- */
    //  Handle
    /* --------------------------------------------------------------------- */
    HWND& Handle() { return handle_; }
    const HWND& Handle() const { return handle_; }

protected:
    /* --------------------------------------------------------------------- */
    /*
     *  Close
     *
     *  EndDialog を呼び出す際に (HWND, CommonDialog*) のマップから
     *  削除する必要があるので,代わりにこちらを使用する.
     */
    /* --------------------------------------------------------------------- */
    void Close(int result) {
        GetInstanceMap().erase(this->Handle());
        EndDialog(this->Handle(), result);
    }
    
    /* --------------------------------------------------------------------- */
    /*
     *  WndProc
     *
     *  メッセージ毎に対応するメンバ関数へ分岐させる.
     */
    /* --------------------------------------------------------------------- */
    virtual BOOL WndProc(UINT uMsg, WPARAM wParam, LPARAM lParam) {
        switch (uMsg) {
        case WM_INITDIALOG: return this->OnCreate(wParam, lParam);
        case WM_COMMAND:    return this->OnCommand(wParam, lParam);
        // ...
        default: braek;
        }
    }
    
    virtual BOOL OnCreate(WPARAM wParam, LPARAM lParam) { ... }
    virtual BOOL OnCommand(WPARAM wParam, LPARAM lParam) { ... }
    // ...
    
private:
    /* --------------------------------------------------------------------- */
    /*
     *  GetInstanceMap
     *
     *  HWND と CommonDialog クラスのインスタンスの対応関係を保持するため
     *  のマップ.StaticWndProc 関数がこのマップを利用して,該当する
     *  クラスの WndProc へと分岐させる.
     *
    /* --------------------------------------------------------------------- */
    typedef std::map<HWND, EncodingDialog*> instance_map_type;
    static instance_map_type& GetInstanceMap() {
        static instance_map_type v_;
        return v_;
    }
    
    /* --------------------------------------------------------------------- */
    //  StaticWndProc
    /* --------------------------------------------------------------------- */
    static BOOL CALLBACK StaticWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
        instance_map_type& v = GetInstanceMap();
        if (uMsg == WM_INITDIALOG) {
            assert(v.find(hWnd) == v.end());
            
            CommonDialog* instance = reinterpret_cast<CommonDialog*>(lParam);
            instance->Handle() = hWnd;
            v.insert(std::make_pair(hWnd, instance));
            break;
        }
        
        instance_map_type::iterator pos = v.find(hWnd);
        if (pos != v.end()) return pos->second->WndProc(uMsg, wParam, lParam);
        return FALSE;
    }
};

こう言った基底クラスをあらかじめ定義しておき,ユーザはこの基底クラスを継承して必要な OnXXX メンバ関数をオーバーライドすると言う方法を取ることで,WndProc 関数がカオスになると言う状況を回避できる事が期待できます.既にある実装だと Win32++ と言うプロジェクトがこの方針を基に各種ウィンドウ/ダイアログクラスを定義しているようです.使う機会がなかったのでちょっと追っていませんが,MFC もこの方針で実装されていたのでしょうか.

問題としては,WM_COMMAND に必要な処理が集中すると言うケースが多いため,この方針で関数に分離していっても OnCommand() メンバ関数がカオスになりがちです.この辺りは,ユーザ (継承したクラスを書く人)が注意しながら書く位しか今のところは思いつきません.

RegistryKey.GetValue() を C++ でどう表現するか?

Win32 API のうち,よく使うものについてはラッパクラスを書いていこうと思っています.今は,C++ (Win32 API) か C# (.NET Framework) でコードを書くことが多いので,Win32 API のラッパクラスのインタフェースは .NET Framework に似せて定義していく事を考えています.

C# から C++ に移植する上で問題となりやすいものの一つに,「Object 型で返される関数を C++ でどう表現するか?」と言うものがあるように思います.例えば,ある特定のレジストリ値を取得するための C# の関数 (メソッド) の定義は以下のようになっています.

public Object GetValue(string name)

これを C++ で表現する方法は,パッと思いついた限り,以下の 3 つの方法があります.

  1. GetValueXXX() のように型によって別々の名前の関数を定義する.
  2. boost::any を使う.
  3. GetValue() をテンプレート関数にして特殊化する.

GetValueXXX() のように型によって別々の名前の関数を定義する

この方法は,Active Template Library (ATL) でも取られており,最もシンプルな方法になるかと思います.今回は別の解決策を取りましたが,この方法が最も無難かなぁと言う気はします.

boost::any を使う

C# の Object 型の代替手段として boost::any を使うと言うのも思いつきやすい方法の一つです.boost::any を使うと GetValue() の定義,および使用側のコードは以下のような形になります.

class RegistryKey {
public:
    typedef TCHAR char_type;
    typedef std::basic_string<TCHAR> string_type;
    typedef boost::any object_type;
    
    object_type GetValue(const string_type& name) {
        object_type dest;
        
        // 必要な処理 ...
        
        return dest;
    }
};

int main(int argc, char* argv[]) {
    RegistryKey subkey = Registry::CurrentUser().OpenSubKey(_T("foo"));
    DWORD value = boost::any_cast<DWORD>(subkey.GetValue(_T("bar")));
    return 0;
}

使用感は,この方法が最も C# の該当関数に近いものになるような気はします.

GetValue() をテンプレート関数にして特殊化する

最終的に,今回採用した方法はこれでした.この方法は,はじめテンプレート・メンバ関数は特殊化できないものと思い込んでいたので敬遠していたのですが,関数テンプレートの特殊化について - memologue を見ると,クラスのスコープ外に記述すれば可能なようです.

class RegistryKey {
public:
    typedef TCHAR char_type;
    typedef std::basic_string<TCHAR> string_type;
    
    template <class ObjectT>
    ObjectT GetValue(const string_type& name);
};

// DWORD, std::string 等で特殊化していく
template <>
inline DWORD RegistryKey::GetValue(const string_type& name) {
    // ...
}

int main(int argc, char* argv[]) {
    RegistryKey subkey = Registry::CurrentUser().OpenSubKey(_T("foo"));
    DWORD value = subkey.GetValue<DWORD>(_T("bar"));
    return 0;
}

まだ途中ですが,https://github.com/clown/psdotnet でこの方法でのラッパを公開しています.