今日はこんな話です。
- boost::in_placeを使ってみようとした。
- エラーになる例に遭遇。
- Boost側を直せばいい(そのためには、こんなの誰が知っているんだという::new構文の出番)
この前、Gdiplusを使っていたプログラムでちょうどin_placeとoptionalの組み合わせが使えそうだと気付きました。ちょうど信仰「ワンフェーズコンストラクティズム」やTogetter – まとめ「N3059」が世に公表されるなどという時流だったわけです。
ところが、ダメでした。謎のエラーになるのです。そのときは原因追及を諦めてboost::scoped_ptrで我慢しました。こんなコードで同じエラーを見られます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | #include <new> #include <memory> #include <boost/optional.hpp> #include <boost/utility/in_place_factory.hpp> #include <windows.h> #include <gdiplus.h> #if 1 // wchar_t constとfloatを引数に取るコンストラクタを持っている。 using Gdiplus::Font; #else struct Font { Font(wchar_t const*, float) {} }; #endif int main() { std::unique_ptr<Font> pf; boost::optional<Font> of; pf.reset((new Font(L"Arial", 16.f))); of = boost::in_place(L"Arial", 16.f); } |
余談ですが、この引数で呼ばれるGdiplus::Fontコンストラクタにはデフォルト引数があるので、実際の引数の個数はもっと多いです。
さて#ifの条件の値を0にする、すなわち自作Fontクラスにするとエラー無くin_placeが使用可能です。なぜ、この自作Fontクラスはin_placeが利用可能で、Gdiplus::Fontはだめだったのでしょうか?
その答えはnew演算子の多重定義です。
Gdiplus::Fontの基底クラスを辿ると、GdiplusBaseクラスが親にあります。
1 2 3 4 5 6 7 8 9 | class GdiplusBase { public: void* (operator new)(size_t in_size) { return DllExports::GdipAlloc(in_size); } // ほか色々省略 }; |
これがboost::in_place内部のplacement newの邪魔をしてしまうのです。in_place内部では、インスタンスの作成のために<new>にあるnewの多重定義の1つを呼びます。Boost 1.43.0ではboost/utility/in_place_factory.hppの50行目からその処理があります。
1 2 3 4 5 6 | template<class T> void* apply(void* address BOOST_APPEND_EXPLICIT_TEMPLATE_TYPE(T)) const { return new(address) T( BOOST_PP_ENUM_PARAMS(N, m_a) ); } |
(BOOST_PP_ENUM_PARAMSはここでは無視しましょう)このnewは<new>で定義されている先ほど紹介した多重定義の1つを呼ぶことを意図しているはずです。
にも関わらず、GdiplusBaseがメンバにoperator newを持っているため、Gdiplusとその派生クラスにおいてはこのapply内のnewについてもクラス内からの捜索を始めてしまい、
<new>にあるin_placeが用いるつもりだった先ほど紹介したグローバルなoperator newが見つかりません。このため、引数の適合するoperator newが存在しないことを原因とするエラーが生成されるのです。
解決策はapplyに修正を加えることです(GdiplusBaseをいじる方法もありますが割愛します)。
1 | return ::new(address) T( BOOST_PP_ENUM_PARAMS(N, m_a) ); |
たったこれだけです。これでGdiplus::Fontでもなんでも大丈夫です。
C++標準には、newやdelete演算子に::を前置する構文があります。これは、グローバル名前空間にあるoperator newやdeleteを呼ぶ意味になります。普段使う::なしのnewやdeleteだと、newするクラスを起点として名前探索が行われます。なお、hoge::newのような任意の名前空間を指定する構文はありません。
あとは、こう修正してくれと働きかけるだけです。……、すみませんまだやっていません。
2011年5月10日: 誤字修正しました。
スポンサード リンク |