Подтвердить что ты не робот

Когда компилятор статически привязывает вызов к виртуальной функции?

Я ожидал, что компилятор сможет статически разрешить вызов функции виртуальной функции, если тип класса известен во время компиляции (например, если экземпляр класса не используется через ссылку или указатель, как показано в Случай 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  
}
4b9b3361

Ответ 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 полиморфна там, что может повлиять на оптимизации.

Обратите внимание, что компилятор не решает все возможные случаи, когда небольшая тестовая программа "очевидна" для человека. Фокус компиляторов - это случаи, которые часто случаются в крупных реальных программах.