Вносит ли метод базового класса, помеченный как «virtual final», дополнительные издержки? - PullRequest
2 голосов
/ 10 ноября 2019

Предположим, я хочу иметь класс Base с двумя методами: foo(int) и bar(int). Я хочу, чтобы они были определены таким образом, что:

  • Base::foo должно быть переопределено в производном неабстрактном классе
  • Base::bar не может быть переопределено в производном классе

Первая цель может быть достигнута, пометив foo как virtual int foo(int) = 0, чтобы сделать ее абстрактной. Второе требование можно выполнить, отметив bar как virtual int bar(int) final, чтобы сделать его окончательным. Вот код:

class Base
{
public:
    virtual int foo(int n) = 0;
    virtual int bar(int n) final
    {
        return n + 42;
    }
};

И пример класса, производного от Base:

class Derived : public Base
{
public:
    virtual int foo(int n) override
    {
        return n * n;
    }
    int bar(int n) // compilation error here
    {
        return n + 43;
    }
};

Попытка переопределить Base::bar вызвала ошибку компиляции, как мы и хотели.

Теперь мой вопрос таков: Обозначает ли метод как virtual final накладные расходы из-за функции virtual (динамическая диспетчеризация), даже если функция не может быть переопределена в любом случае?

Редактировать

Не обращайте внимания на отсутствие виртуального деструктора ~Base() Это не здесь, чтобы сделать код короче.

Ответы [ 2 ]

3 голосов
/ 10 ноября 2019

Компилятор, скорее всего, извращает этот вызов:

struct Base {
    virtual int bar(int n) final {
        return n + 42;
    }
};

struct Derived : Base { };

int foo(Derived& d, int n) {
    return d.bar(n);
}

становится с -O1:

foo(Derived&, int):
        lea     eax, [rsi+42]
        ret

, тогда как без final мы получаемкосвенный вызов:

foo(Derived&, int):
        sub     rsp, 8
        mov     rax, QWORD PTR [rdi]
        call    [QWORD PTR [rax]]
        add     rsp, 8
        ret
1 голос
/ 10 ноября 2019

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

Вот это из реального кода:

b->FoA(); //virtual inherited
002829A7  mov         eax,dword ptr [b]  
002829AA  mov         edx,dword ptr [eax]  
002829AC  mov         esi,esp  
002829AE  mov         ecx,dword ptr [b]  
002829B1  mov         eax,dword ptr [edx]  
002829B3  call        eax  
002829B5  cmp         esi,esp  
002829B7  call        __RTC_CheckEsp (02812E9h)  
    b->FoB(); // final
002829BC  mov         ecx,dword ptr [b]  
002829BF  call        A::FoB (0281366h)  
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...