2012-02-25 8 views
1

Я указал this question (я сменил название). Я знаю, что генерация кода, связанная с virtual, специфична для реализации. Однако более ранний вопрос предполагает, что при вызове метода не виртуального базиса существует дополнительная стоимость, связанная с наследованием virtual.Есть ли дополнительная стоимость вызова не виртуальных базовых методов в виртуальном наследовании?

я написал следующие тестовые коды и проверил его сборку в г ++ (с -O4):

Общей частью

struct Base { 
    int t_size; 
    Base (int i) : t_size(i) {} 
    virtual ~Base() {} 
    int size() const { return t_size; }; 
}; 

struct D1 : virtual Base { 
    int a[10]; 
    D1() : Base(0) {} 
    ~D1() {} 
}; 
struct D2 : virtual Base { 
    int a[20]; 
    D2() : Base(0) {} 
    ~D2() {} 
}; 

... 

void foo (Base *p) 
{ 
    if(p->size()) 
    return; 
    p = 0; 
} 

int main() 
{ 
    Derived d; 
    foo(&d); 
} 

Теперь разница части здесь:

Код 1 (нормальное наследование)

struct Derived : Base { 
    Derived() : Base(0) {} 
    ~Derived() {} 
    int a[100]; 
}; 

Код 2 (виртуальное наследование)

struct Derived : virtual Base { 
    Derived() : Base(0) {} 
    ~Derived() {} 
    int a[100]; 
}; 

код 3 (множественное виртуальное наследование)

struct Derived : D1, D2 { 
    Derived() : Base(0) {} 
    ~Derived() {} 
    int a[100]; 
}; 

Overall code here.

Когда я проверил его сборку, есть без разницы между всеми 3 версиями. И Ниже приведен код сборки:

 .file "virtualInheritFunctionCall.cpp" 
     .text 
     .p2align 4,,15 
     .globl _Z3fooP4Base 
     .type _Z3fooP4Base, @function 
_Z3fooP4Base: 
.LFB1: 
     .cfi_startproc 
     rep 
     ret 
     .cfi_endproc 
.LFE1: 
     .size _Z3fooP4Base, .-_Z3fooP4Base 
     .section  .text.startup,"ax",@progbits 
     .p2align 4,,15 
     .globl main 
     .type main, @function 
main: 
.LFB2: 
     .cfi_startproc 
     xorl %eax, %eax 
     ret 
     .cfi_endproc 
.LFE2: 
     .size main, .-main 
     .ident "GCC: (Ubuntu/Linaro 4.6.1-9ubuntu3) 4.6.1" 
     .section  .note.GNU-stack,"",@progbits 

Означает ли это, что virtual наследование не имеет каких-либо дополнительных затрат, когда определенная оптимизация ON? Нужно ли мне выполнять более сложный тестовый код, чтобы оценить это? Обратите внимание, что без оптимизации существует разница между этими сборками.

+1

Какой смысл окончательного 'p = 0;'? 'p' - это только локальная переменная. Кроме того, 'Base' является стандартным макетом, а' D1' и 'D2' пусты, поэтому неудивительно, что есть место для оптимизации. –

+0

Я ожидал бы, что любая дополнительная стоимость придет, когда вы вызываете не виртуальную функцию 'size' через' Derived * ', а не когда вы вызываете ее через' Base * '. Тип 'Base' не знает, действительно ли какой-либо другой класс наследует его, поэтому нет никакого способа, чтобы фактически наследование от« Base »могло привести к накладным расходам при вызове функции в« Base »с помощью указателя на« Base » , –

+0

@SteveJessop, отсутствие разницы в результатах сборки при проверке с помощью 'Derived *'. Я выбираю 'Base *' в контексте более раннего вопроса, где принятый ответ подсказывает это. @Kerrek, я просто написал 'p = 0', так что компилятор не оптимизирует условие' if'. Ничего особенного. – iammilind

ответ

3

Вместо исполнения, я хочу знать, как виртуальное наследование имеет дело с методами невиртуальных базовых

Очевидно, что он будет регулировать this или класс указатель затем передать его оригинальный метод.

Вы могли бы заметить, накладные расходы, если вы настраиваете 3rd пример так:

  1. Добавить виртуальные методы (не перекрывающихся имен) к базовой, D1 и D2. Это заставит компилятор создавать таблицы виртуальных методов.
  2. Добавьте неперекрывающиеся поля данных/переменные-члены (неперекрывающиеся, разные имена) в Base, D1, D2 и производные.
  3. Добавить не виртуальный метод в D2, который работает с полями данных в D2 и Base.
  4. Добавить не виртуальный метод в D1, который работает с полями данных в D1 и Base.
  5. Добавить не-v \ irtual метод в Derived, который вызывает вышеупомянутые не виртуальные методы в D2 и D1, затем работает с полями данных в D2, D1, Base и Derived.
  6. Исследуйте разборку.

Кроме того, как только есть некоторые переменные-члены, вам может понадобиться изучить макет полученного производного класса в отладчике.

Когда я проверил его сборку, нет никакой разницы между всеми 3-й версиями

Наследования (виртуального или нет) может добавить небольшое различие в том смысле, что компилятор может решить, настроить указатель на класс при преобразовании это от Derived * до Base * (или this, если базовый не виртуальный метод вызывается из производного метода) или vfptr. Это приведет к добавлению некоторого значения в текущее значение или указателю перед передачей его функции/методу.

Однако, скорее всего, это будет сделано в момент вызова вызова функции, и это, скорее всего, произойдет только при использовании множественного наследования (поскольку может быть более одной таблицы виртуальных методов). То есть если вы делаете класс C, который наследует классы A и B, и все они имеют виртуальные методы, но нет общих предков, то при вызове метода, который принадлежит A, от C вы можете увидеть корректировки указателя при разборке. Но все. стоимость таких накладных расходов будет смехотворно мала.

Обратите внимание, что это вопрос, связанный с компилятором, и все, что я написал здесь, основано на наблюдении за компилятором microsoft. То есть это «недокументированная функция», в результате, если вы беспокоитесь о производительности, вы должны использовать профилировщик, а не пытаться угадать влияние производительности. В любом случае, главным приоритетом должна быть читаемость кода.

+0

** Код 2 ** - наследование бриллианта. Вместо производительности я хочу знать, как «виртуальное» наследование имеет дело с не виртуальными базовыми методами. – iammilind

+0

@iammilind: Обновлен мой ответ. Это должно продемонстрировать практически все накладные расходы, с которыми вы могли столкнуться. – SigTerm

+0

@iammlind: Кроме того, хотя вы «хотите знать», ответ будет специфичным для компилятора. – SigTerm

2

Во-первых, посмотрите на foo:

void foo (Base *p) 
{ 
    if(p->size()) 
    return; 
    p = 0; 
} 

Поскольку Base::size() не является виртуальным, нет виртуальной отправки накладных с p->size().

Далее рассмотрим, как вы вызываете foo:

int main() 
{ 
    Derived d; 
    foo(&d); 
} 

Здесь вы принимаете адрес экземпляра, тип которого известен статически, то есть, учитывая экземпляр Derived, компилятор может статически определить, как для преобразования этого значения в Base *. Итак, независимо от того, как Derived наследует от Base, компилятор знает, как его преобразовать.

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

+0

Сделано [некоторые изменения кода] (http://ideone.com/DtJGY), однако нет никакой разницы между сборками. – iammilind

+0

@iammilind, вы просто добавили оператор if. Компилятор по-прежнему знает, как преобразовать из 'Derived' или' Derived2' в 'Base', т. Е. Без нажатия на vtable. – MSN