Существуют ли дополнительные затраты на вызов не виртуальных базовых методов в виртуальном наследовании? - PullRequest
1 голос
/ 25 февраля 2012

Я сослался на этот вопрос (я изменил его название). Мне известно, что генерация кода, связанная с virtual ness, зависит от конкретной реализации. Однако предыдущий вопрос предполагает, что при вызове не виртуального базового метода возникает дополнительная стоимость, связанная с наследованием virtual.

Я написал следующие тестовые коды и проверил их сборку в g ++ (с -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];
};

Общий код здесь .

Когда я проверял его сборку, между всеми 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 не требует дополнительных затрат, когда включена определенная оптимизация? Нужно ли выполнять какой-либо более сложный тестовый код, чтобы оценить это? Обратите внимание, что без оптимизации существует разница между этими сборками.

Ответы [ 2 ]

3 голосов
/ 25 февраля 2012

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

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

Возможно, вы сможете наблюдать накладные расходы, если настроите третий пример следующим образом:

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

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

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

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

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

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

2 голосов
/ 28 февраля 2012

Во-первых, взгляните на 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, компилятор знает, как его преобразовать.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...