今日はこんな話です。

  1. boost::in_placeを使ってみようとした。
  2. エラーになる例に遭遇。
  3. 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日: 誤字修正しました。


スポンサード リンク

この記事のカテゴリ

  • ⇒ 知ってて良かった::new
  • ⇒ 知ってて良かった::new