間違いなく、これは Visual C++ コンパイラのバグです。Property<T>::IValue
何らかの奇妙な理由で、コンパイラは参照元がポリモーフィックな基底クラスであり、実行時の型情報の代わりにshared_ptr
静的な型情報を式に使用していると判断できません。typeid
バグを再現するための最小限のコード:
template <class T> struct Property
{
struct IValue
{
void f() {}
virtual ~IValue() = 0 {}
};
struct Value: IValue {};
Property(): m_pValue(new Value())
{
// typeid inside class works as expected
std::cout << "Inside class: " << typeid(*m_pValue).name() << std::endl;
}
// If changed to unique_ptr typeid will work as expected
// But if changed to raw pointer the bug remains!
std::shared_ptr<IValue> m_pValue;
};
int main()
{
Property<int> p;
// If next line uncommented typeid will work as expected
//p.m_pValue->f();
std::cout << "Outside class: " << typeid(*p.m_pValue).name() << std::endl;
}
このサンプルを少し試してみたところ、指定されたオブジェクトの関数が前に呼び出された場合、またはが に置き換えられた場合typeid
に、期待どおりに動作し、実行時の型名が表示されることがわかりました。面白いですが、に置き換えてもバグが再現されます!typeid
shared_ptr
unique_ptr
shared_ptr<IValue>
IValue*
実際の出力:
クラス内: struct Property<int>::Value
外部クラス: struct Property<int>::IValue
期待される (正しい) 出力:
クラス内: struct Property<int>::Value
外部クラス: struct Property<int>::Value
アセンブラのリストから、それが本当にコンパイラの問題であることは明らかです。
typeid
call from Property
ctor は への正直な呼び出しを生成します__RTtypeid
:
; 225 : std::cout << typeid(*m_pValue).name() << std::endl;
... irrelevant code skipped ...
; __type_info_root_node
push OFFSET ?__type_info_root_node@@3U__type_info_node@@A
mov ecx, DWORD PTR _this$[ebp]
; std::tr1::shared_ptr<Property<int>::IValue>::operator*
call ??D?$shared_ptr@UIValue@?...<skipped>
push eax
call ___RTtypeid
typeid
外部から呼び出されると、静的なIValue
型情報が生成されます。
; 237 : std::cout << typeid(*p.m_pValue).name() << std::endl;
... irrelevant code skipped ...
; __type_info_root_node
push OFFSET ?__type_info_root_node@@3U__type_info_node@@A
mov ecx, OFFSET ??_R0?AUIValue@?$Property@H@@@8
また、VS2008 にも同じバグが存在するため、古い動作であることに注意してください。