Наши партнеры

UnixForum





Библиотека сайта rus-linux.net

На главную -> MyLDP -> Программирование и алгоритмические языки


Ulrich Drepper "Как писать разделяемые библиотеки"
Назад Оглавление Вперед

2.4.5. Таблицы виртуальных функций языка C++

Специальным случаем являются таблицы виртуальных функции, генерируемые для фукнций членов класса C++, которые помечены как virtual. В таблицах обычно хранится указатель на функции, в результате чего, как уже было показано ранее, от компоновщика потребуется создать перемещения. Стоит также рассмотреть затраты времени исполнения, которые возникают при использовании виртуальных функций для вызовов внутри одного объекта DSO и между различными объектами DSO. Но сначала рассмотрим вопросы, связанные с перемещением.

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

Внимания заслуживает еще один факт. Несмотря на то, что во время выполнения, как правило, используется только одна таблица виртуальных функций (поскольку имя таблицы одно и тоже, то будет использована первая таблица, обнаруженное в области видимости), проинициализировать необходимо все таблицы. Динамический компоновщик не может определить, будет ли таблица использоваться в какой-то момент или нет, и, следовательно, не может ее не инициализировать. По этой причине, если во всех задействованных объектах DSO будет использоваться только одно определение таблицы виртуальных функций, то это окажется полезным не только для экономии места.

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

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

struct foo {
	virtual int virfunc () const;
};
foo var;
int bar () { return var.virfunc (); }

Причина этого в том, что известно, что переменная var имеет тип foo, а не тип, для определения которого используется таблица виртуальных функций. Если экземпляр класса foo есть в другом объекте DSO, то из этого объекта DSO будет экспортирована не только таблица виртуальных функций, также и виртуальная функция virfunc.

Если допустимы очень небольшие дополнительные накладные времени выполнения, то виртуальную функцию и интерфейс функции, используемый извне, следует разделить. Например, следующим образом:

/* В заголовке.  */
struct foo {
	virtual int virfunc () const;
	int virfunc_do () const;
};

/* В файле с исходным кодом.  */
virtual int foo::virfunc () const
{  return virfunc_do (); }
int foo::virfunc_do () const
{  ...что-то делаем... }

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

Следствием того, что виртуальная функция скрыта, будет использование относительного перемещения для хэш-блока таблицы виртуальных функций в virfunc. Большое преимущество здесь связано не только с тем, что этот тип перемещения должен обрабатываться намного быстрее. Т.к. В таблицах виртуальных функций находятся ссылки на данные, перемещение хэш-блоков таблицы виртуальных функций должно выполняться во время запуска приложения.

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

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


Предыдущий раздел:   Следующий раздел:
Назад Оглавление Вперед