Когда компилятор статически привязывает вызов к виртуальной функции?
Я ожидал, что компилятор сможет статически разрешить вызов функции виртуальной функции, если тип класса известен во время компиляции (например, если экземпляр класса не используется через ссылку или указатель, как показано в Случай 1) ниже).
Однако я наблюдал странное поведение с компилятором Visual Studio 2010 С++, и я хотел бы знать, есть ли какая-либо причина для компилятора не статически привязывать вызовы к "правильной" виртуальной функции, когда экземпляры классов с виртуальными функциями являются членами в структуре, к которой обращается ссылка.
Должен ли я ожидать, что компилятор будет статически привязывать вызовы к f() в примере 2) ниже? Является ли "ссылка" cr каким-то образом распространяться на cr.a, хотя a
является a
, а не A&
?
struct A
{
virtual void f() ;
virtual ~A() ;
};
struct B : A
{
virtual void f() ;
virtual ~B() ;
};
struct C {
A a ;
B b ;
};
C & GetACRef() ;
void test()
{
// Case 1) The following calls to f() are statically bound i.e.
// f() is called without looking up the virtual function ptr.
C c ;
c.a.f() ;
c.b.f() ;
A a ;
a.f() ;
// Case 2) The following calls to f() go through the dynamic dispatching
// virtual function lookup code. You can check if you generate the .asm
// for this file.
C & cr = GetACRef() ; // Note that C is not polymorphic
cr.a.f() ; // visual C++ 2010 generates call to f using virtual dispatching
cr.b.f() ; // visual C++ 2010 generates call to f using virtual dispatching
}
Ответы
Ответ 1
Я не знаю, почему MSVC не скомпилирует сценарии "Case 2" для прямых вызовов - это, безусловно, возможно. Я думаю, что только Microsoft сможет ответить.
Обратите внимание, что GCC выполняет оптимизацию, которую вы ищете (проверена с помощью MinGW 4.5.1 и -O2
).
Кроме того, MSVC использовал диспетчер vtable даже для следующей последовательности (для ясности - я использую опцию оптимизации /Ox
):
A a;
A& ar(a);
ar.f();
Поэтому нет необходимости, чтобы структура функций или контейнеров добавляла к потенциальным путаницам потенциальные путаницы - нет причин, по которым компилятор не может обрабатывать ar.f()
точно так же, как a.f()
в этой последовательности. Но, как предположил Бо Перссон, возможно, это не очень распространенный сценарий оптимизации (или MS просто не дошли до него). Опять же, могут ответить только разработчики компилятора в MS.
Я не уверен, что классифицировал бы это поведение как "странное", но это была возможность оптимизации, которую пропустили. Я не уверен, насколько распространенным может быть такое. Должны ли вы ожидать, что компилятор будет генерировать статически связанные вызовы в этом случае? Может быть. Но я думаю, что это не слишком удивительно, что этого не происходит.
Возможно, проблема должна быть открыта в MS Connect.
Ответ 2
Очевидно, авторы компилятора не потрудились решить этот случай. Возможно, это недостаточно распространено в реальном коде, чтобы заслужить их внимания.
Если GetACRef
определяется в другом месте, также возможно, что C полиморфна там, что может повлиять на оптимизации.
Обратите внимание, что компилятор не решает все возможные случаи, когда небольшая тестовая программа "очевидна" для человека. Фокус компиляторов - это случаи, которые часто случаются в крупных реальных программах.