У меня есть вопрос относительно оптимизации вызовов виртуальных функций. Я где-то читал (и проблема в том, что я сейчас не могу найти статью), что можно было бы оптимизировать поиск по v-таблице с помощью конструкции, подобной этой:
// Base.h
class Base
{
public:
virtual void Foo() = 0;
};
// Concrete.h
class Concrete : public Base
{
public:
virtual void Foo()
{
// do something;
}
};
//Some.h
extern Base* const g_object;
// Some.cpp
Concrete on_stack_concrete;
Base* const g_object = &on_stack_concrete;
Предполагается, что хитрость заключается в использовании константного указателя на переменную, которая выделяется в стеке (не динамически), и что компилятор наверняка оптимизирует это. Поэтому всякий раз, когда пользователь вызывает g_object-> Foo (), часть // выполнения чего-либо будет выполнена без необходимости поиска в v-таблице.
Это правда или нет?
Заранее спасибо за любой повтор.
РЕДАКТИРОВАТЬ:
Возможное использование такой конструкции заключается в ограничении интерфейса конкретных реализаций. Конечно, можно утверждать, что «ограниченные» методы должны быть закрытыми, но иногда другим модулям библиотеки требуется доступ к этим общедоступным дополнительным методам объекта, не позволяя пользователю манипулировать ими. Так, например, используя #defines, можно создать код, подобный следующему:
// Some.cpp
#ifdef _WIN32
Win32Concrete concrete;
#elif defined _UNIX
UnixConcrete concrete;
#endif
Base* const g_global = &concrete;
Фактически объявление этих классов может быть определено только в файле CPP, поэтому пользователь не знает об их существовании.
Вопрос не Зачем в первую очередь использовать такой константный указатель, но если это возможно, оптимизировать поиск в v-таблице в таком сценарии.
Вы, кажется, неправильно используете virtual
,
virtual
реализует полиморфизм во время выполнения. И сценарий, который вы описываете, не использует или не нуждается в этом. Вряд ли оба Win32Concrete
а также UnixConcrete
существуют в любой среде компиляции.
Вместо:
// Some.cpp
#ifdef _WIN32
Win32Concrete concrete;
#elif defined _UNIX
UnixConcrete concrete;
#endif
Base* const g_global = &concrete;
использовать:
// CommonHeader.h
#ifdef _WIN32
typedef Win32Concrete Concrete;
#elif defined _UNIX
typedef UnixConcrete Concrete;
#endif
Теперь ваши функции не должны быть виртуальными.
Самый простой способ решить эту проблему — создать классы, которым нужен доступ к ограниченному методу, друзей класса Concrete. Затем они получают полный доступ к классу, а все остальные получают только публичный доступ.
Если это не практично, вы можете поместить реализацию в базовый класс со всеми защищенными ограниченными методами, а затем получить специальный класс, который предоставляет защищенные методы.
class Concrete
{
public:
void foo() { ... }
protected:
void bar() { ... }
};
class ConcretePrivate : public Concrete
{
public:
void bar() { Concrete:: bar(); }
};
ConcretePrivate g_globalPrivate;
Concrete& g_global = g_globalPrivate;
Код, который использует g_global
может получить доступ только к конкретным методам. Код, который использует g_globalPrivate
может получить доступ к методам ConcretePrivate.
Этот подход используется в исходном коде Doom 3 (https://github.com/id-Software/DOOM-3-BFG/), например, neo / framework / FileSystem.h определяет это:
extern idFileSystem * fileSystem;
И neo / framework / FileSystem.cpp определяет это:
idFileSystemLocal fileSystemLocal;
idFileSystem * fileSystem = &fileSystemLocal;
Единственное обсуждение, которое я мог найти об этом: http://fabiensanglard.net/doom3/
Все объекты высокого уровня idTech4 — это абстрактные классы с виртуальными методами. Обычно это связано с падением производительности, поскольку каждый виртуальный адрес метода должен быть найден в виртуальной таблице перед вызовом во время выполнения. Но есть «хитрость», чтобы этого избежать.
Поскольку объект, размещенный статически в сегменте данных, имеет известный тип, компилятор может оптимизировать поиск в vtable при вызове методов commonLocal.