Виртуальные вызовы при наследовании

#c #inheritance

#c #наследование

Вопрос:

Как я узнаю, разрешен ли вызов функции во время компиляции или выполнения из какого-либо класса?

Например, В следующем примере, из производного класса при show() вызове, будет ли это разрешено во время выполнения?

 #include <iostream>
using std::ostream;

class Base 
{
public:
    virtual void show() {

         show(); //Call derived class 'show()'
    }
};

class Derived : public Base {

public:

    void show() {

         show(); //Call to itself, Would this call be resolved at run-time?
    }
};

ostreamamp; operator <<(ostream amp;os, Base amp;obj)
{

    obj.Base::show();
    return os;
}

int main()
{
    Derived D;

    cout << D << endl;
}
  

Комментарии:

1. Просто скомпилируйте в список сборок с помощью -S. Очень легко увидеть разницу между виртуальной отправкой (во время выполнения) и обычным вызовом функции.

2. @Mikael: «виртуальная отправка … обычный вызов функции» — если это варианты, то это действительно не имеет значения с точки зрения производительности: что может иметь значение, так это когда это виртуальная отправка, а не встраивание.

3. Я обеспокоен тем, что вы неправильно понимаете виртуальную отправку: Base::show() не следует вызывать show() … она не вызывается, когда доступна виртуальная перегрузка, реализация производного класса вызывается напрямую. Derived::show() не следует вызывать show() — это будет повторяться бесконечно, пока не закончится пространство стека.

Ответ №1:

Всякий раз, когда вы вызываете функцию-член через объект конкретного типа (т. Е. без указателя), статический тип известен, поэтому компилятор разрешает нужную функцию во время компиляции. Виртуальные функции разрешаются во время выполнения только тогда, когда вы вызываете их по указателю на объект, используя полиморфизм.

В вашем примере вызовы show() выполняются через указатель «this», и впоследствии они будут разрешены во время выполнения. Учтите, что всегда может существовать класс, находящийся еще дальше по цепочке наследования, который реализует show().

Явно определенный вызов «obj.Base::show()», очевидно, разрешен во время компиляции.

Ответ №2:

Всякий раз, когда компилятор может определить, какую перегрузку вашей функции вызывать, он это сделает. Это гарантированно будет возможно сделать, когда

  • он имеет полный тип объекта (например Foo foo; foo.bar(); )
  • вы указываете ему, какую перегрузку вызывать (например, Foo foo; foo.Bar::bar(); )

но он может быть в состоянии сделать это в случаях, когда это менее очевидно — т. Е. Если он может выяснить, что «этот указатель на Foo действительно всегда указывает на Bar «. Это называется девиртуализацией и является частью защитных очков оптимизирующего компилятора. В зависимости от вашего компилятора и в зависимости от вашего реального кода может оказаться возможным выполнить эту оптимизацию — и вызвать вашу функцию напрямую, не проходя через vtable.

Комментарии:

1. В этих случаях вы можете использовать CRTP (любопытно повторяющийся шаблон шаблона), если хотите получить гарантированный статический полиморфизм, независимо от степени волшебства, происходящего внутри компилятора.

2. если вы хотите, чтобы это было гарантировано, конечно, но CRTP не всегда возможен в реальной жизни, а девиртуализация — это не то, на что вам следует рассчитывать — это то, что может быть приятным, если компилятор может сделать это за вас

Ответ №3:

Чтобы ответить на ваш вопрос, в вашем коде obj.Base::show() решается во время компиляции из-за явного вызова базовой функции. Если вы хотите решить эту проблему во время выполнения, то вы можете использовать указатель на базу и передать ему указатель на производную.

Например:

 ostreamamp; operator <<(ostream amp;os, Base *obj)
{    
    obj->show();
    return os;
}

int main()
{
    Derived D;    
    cout << amp;D << endl;
}
  

Я не уверен, что вы пытаетесь сделать. Из вашего кода кажется, что вы хотите вызвать производную (полиморфную, разрешенную во время выполнения версию) функцию вашего базового класса ‘show()’. В этом нет необходимости, поскольку производная версия вызывается автоматически, поскольку она виртуальная.