shared_ptrとHDCとSelectObjectと
「HDCやSelectObjectしたHGDIOBJの寿命管理をするスマートな方法が無いか」という独り言に勝手に答えてみました。
Motivation
- shared_ptrをマンセーする
- ctor/dtorのためだけのクラスを作りたくない
Approach
- Win32 HANDLEをboost::shared_ptr (or std::tr1::shared_ptr)で管理する
- 他の方法での寿命管理は行なわない
Preparation
includeとかtypedefとか。
#include <windows.h> #include <boost/shared_ptr.hpp> #include <cassert> /// HWNDの実体の名前を取り出すメタ関数 template <typename T> struct deref_ptr; template <typename T> struct deref_ptr<T *> { typedef T type; }; using boost::shared_ptr; //< std::tr1::shared_ptrでもいいはず typedef shared_ptr<deref_ptr<HWND>::type> shared_hwnd; typedef shared_ptr<deref_ptr<HGDIOBJ>::type> shared_hgdiobj; typedef shared_ptr<deref_ptr<HDC>::type> shared_hdc; typedef shared_ptr<deref_ptr<HPEN>::type> shared_pen; typedef shared_ptr<deref_ptr<HBRUSH>::type> shared_brush; typedef shared_ptr<void> selobj_duration; //< これが今回の主役 typedef COLORREF color_t; /// おまけ。 inline bool line_to(shared_hdc dc, int x, int y) { return LineTo(dc.get(), x, y); }
deref_ptrはきっとboost探せばあるだろうけど、面倒コンパイル遅くなりそうなので自前で用意。
実際はcolor_tももう少し真面目に作ったほうがよさそう(移植性的な意味で)。
Factories/Deleters
ファクトリ・デリータたち。
/// デバイスコンテキストを取ってくる inline shared_hdc get_dc(shared_hwnd h) { return shared_hdc(GetDC(h.get()), release_dc_fn(h)); } /// ペンを作る inline shared_pen create_pen(int style, int width, color_t color) { return shared_pen(CreatePen(style, width, color), DeleteObject); } /// ブラシを作る inline shared_brush create_solid_brush(color_t color) { return shared_brush(CreateSolidBrush(color), DeleteObject); } /// GetDCで取ったHDCを殺す struct release_dc_fn { release_dc_fn(shared_hwnd hw_): hw(hw_) { } void operator ()(HDC dc) { ReleaseDC(hw.get(), dc);} shared_hwnd hw; };
これもrelease_dc_fnはboost::bindとか使えそうだけど、コンパイル時間ry
Principal
今日の主役。
簡単に言うと、"SelectObjectされた状態"を寿命管理するという考え方。
selobj_duration自体には"生きている"以上の価値はありません。
selobj_durationが死ぬと、"SelectObjectされた状態"が終わったとみなし、
元のHGDIOBJでSelectObjectをしなおします。
/// selobj_durationが死んだときに元のHGDIOBJにSelectObjectしなおす struct rollback_select_object_fn { rollback_select_object_fn(shared_hdc dc_, shared_hgdiobj cur_): dc(dc_), cur(cur_) { } void operator ()(HGDIOBJ old) { assert(cur.get() == SelectObject(dc.get(), old)); } shared_hdc dc; shared_hgdiobj cur; }; /// 新しいHGDIOBJにSelectObjectして、元のHGDIOBJ・shared_hdc・shared_hgdiobjを持ったselobj_durationを返す inline selobj_duration select_object(shared_hdc dc, shared_hgdiobj obj) { HGDIOBJ old = SelectObject(dc.get(), obj.get()); return shared_hgdiobj(old, rollback_select_object_fn(dc, obj)); }
selobj_durationのDeleterにshared_hdcを持たせることで、勝手に元のHDCが開放されないことを保障しています。
また、新しいHGDIOBJを持っているshared_hgdiobjを持たせてassertをかけていますが、これは状況によっては邪魔かもしれません。
これもboost::bindで出来る、はず。きっと。
Usage
int main() { shared_hdc dc = get_dc(shared_hwnd()); { selobj_duration sel0; { shared_pen pen1 = create_pen(PS_SOLID, 5, RGB(0x20, 0x40, 0x60)); shared_pen pen2 = create_pen(PS_SOLID, 10, RGB(0x60, 0x40, 0x20)); shared_brush brush = create_solid_brush(RGB(0x80, 0xA0, 0xC0)); { selobj_duration sel1 = select_object(dc, pen1); // a line_to(dc, 100, 100); } // A line_to(dc, 200, 200); sel0 = select_object(dc, pen2); // b line_to(dc, 300, 300); } // B line_to(dc, 400, 400); } // C line_to(dc, 500, 500); } // D
ctor/dtorのログを取れるようにするのが面倒だったのでやってませんが、
a地点でpen1にSelectObjectされたものが、A地点で元に戻り、2回目のline_toではデフォルトのペンに戻っています。
また、b地点でpen2にSelectObjectしていますが、sel0の寿命はC地点まで残っているため、4回目のline_toの時点でもpen2は生き残っていて、これを使って描画しています。
そして、C地点にたどり着くとsel0も消えるため、5回目のline_toはデフォルトのペンで描画します。
このようにペンやブラシの寿命が先に来ても、SelectObjectされている限り勝手に消えることがなくなり、神経質に寿命管理をしなくてもよくなりました。
shared_ptrってすばらしい。
shared_ptr最高です。
shared_pt(ry
Reference
http://www.slideshare.net/Cryolite/boost-pdf-2 (id:Cryolite 先生)
http://www.boost.org/doc/libs/1_41_0/libs/smart_ptr/shared_ptr.htm
@sscrisk先生
@egtra先生