2015-02-04 7 views
1

Рассмотрим программу, которая имеет класс Foo, содержащий функцию Foo::fn объявленную как это:Виртуальная эффективность функции и «окончательный» ключевое слово

virtual void fn(); 

и подкласс Foo под названием Bar. Будет ли объявить Bar::fn как это:

virtual void fn() override final; 

вызывают призывы к fn в Bar или подклассов Bar, чтобы быть более эффективным, или это будет просто держать подклассы Bar переопределение fn? Если вызовы становятся более эффективными с использованием final, то какой самый простой и эффективный способ определить Bar::fn так, чтобы его функциональность была точно такой же, как и у Foo::fn?

+1

No. Компилятор даже не знает, что 'Bar' существует (при условии, наиболее распространенный случай, когда он находится в другой единицы перевода с отключенной оптимизацией цельной программы), когда он определяет, как компилировать код, который вызывает 'Foo :: fn'. Почему вы беспокоитесь о размере vtable? – Cameron

+0

Я подозреваю, что таблица vtable остается того же размера, но окончательные отметки, поэтому компилятор не разрешает переопределения в производных классах – cppguy

+3

Обычно в классах нет vtables. Экземпляры содержат vptrs. –

ответ

2

Если fn определяются как final в Bar, компилятор может отправлять вызовы на fn через указатель или ссылку на Bar статический, так как он знает, что Bar::fn является окончательной подменой. Например, этот фрагмент программы:

struct Foo { 
    virtual void fn(); 
}; 

struct Bar : Foo { 
    void fn() final override; 
}; 

void with_foo(Foo& o) { o.fn(); } 
void with_bar(Bar& o) { o.fn(); } 

компилируется (See gcc.godbolt.org for details):

with_foo(Foo&): 
    subq $8, %rsp 
    movq (%rdi), %rax 
    call *(%rax) 
    addq $8, %rsp 
    ret 

with_bar(Bar&): 
    subq $8, %rsp 
    call Bar::fn() 
    addq $8, %rsp 
    ret 

вызов в with_foo динамически отправляется (call *(%rax) косвенный вызов) через виртуальные таблицы, но вызов в with_bar статически отправляет до Bar::fn().

Самый простой способ сделать Bar::fn быть окончательным подмены из Foo::fn без изменения поведения, чтобы определить его статически называют Foo::fn:

struct Bar : Foo { 
    void fn() final override { Foo::fn(); } 
}; 
+0

Мои знания сборки несколько минимальны, но, похоже, это то, что я искал ... По сути, это показывает, что вызов 'Bar :: fn 'более эффективен, когда экземпляр' Bar' хранится как 'Bar', но нет усиления, когда он хранится как' Foo', правильно? – Conduit

+1

@Conduit Исправить. В общем, нет никакого способа, чтобы компилятор статически определял, что 'Foo *' или 'Foo &' фактически ссылается на экземпляр 'Bar'. – Casey

+0

Имеет смысл, учитывая небольшую мысль. Именно то, что я искал тогда - спасибо! – Conduit

3

Я НИКОГДА не заботился о размере vtable. Это обычно относительно мало, и есть только одно объявление класса. То, что намного более надоедливо, - это дополнительное пространство, занимаемое экземплярами класса, поскольку, за исключением одиночных, экземпляры классов часто бывают многих. Поэтому добавление дополнительных элементов в класс, так или иначе, безусловно повлияет на объем памяти. Если это ДЕЙСТВИТЕЛЬНО беспокоит вас, что vtable слишком велик, то выполните некоторую редизайн, чтобы не было так много различных виртуальных функций-членов (возможно, разделение иерархии классов на несколько классов) или меньше производных классов. Но действительно, даже если у вас есть сотни классов, каждый из которых имеет сотню функций виртуального члена, он по-прежнему относительно невелик - 200 классов со 100 членами будут занимать 20000 * 8 байт на запись [64-разрядная архитектура] -> 160 КБ. Разумеется, 20000 функций [да, теоретически, вам нужна только одна новая функция для производного класса для новой виртуальной таблицы, но это довольно глупый дизайн, настолько маловероятный на самом деле]

Назначение ключевого слова final убедитесь, что вы не извлекаете из него дальше - это полезно, например, если у вас есть базовая иерархия классов, где некоторая конкретная функция не должна «изменяться». Скажем, например, у вас есть:

class user_base 
{ 
    public: 
     virtual bool check_password(); {... magical code .. }; 
     virtual bool is_super_user() = 0; 
}; 

class superuser : public user_base 
{ 
    public: 
     virtual bool check_password() final 
     { .... magical code ... 
      ... extra checks to ensure no remote login... 
     } 
     virtual bool is_super_user() final { return true; } 

}; 

class user : public user_base 
{ 
    public: 
     virtual bool is_super_user() final { return false; } 
}; 

Вы должны обмануть вокруг немного, чтобы убедиться, что user_base не используется в качестве базового класса для fake_super_user, и, конечно же, есть и другие вопросы, BIG безопасности с такой дизайн, но это дает вам некоторую идею.

+0

Я думаю, что это в значительной степени покрывает то, что я искал, предполагая, что я прав, когда вы заявляете, что «final» используется только для того, чтобы подклассы не могли переопределить эту функцию, но не влияют на посткомпиляцию результатов. Похоже, я могу злоупотреблять своей терминологией ... Я выхожу из вашего ответа, что vtables хранятся в классе. Что хранится с фактическими экземплярами? – Conduit

+0

Ничего - нашел то, что искал. Отредактированный вопрос. – Conduit

+2

Класс содержит указатель на vtable (ы) для экземпляра. [У вас может быть несколько vtables в случаях, когда есть множественное наследование] –