У меня есть класс с двумя виртуальными функциями-членами: foo
а также wrapper
, foo
короткий и быстрый, и wrapper
содержит цикл, который вызывает foo
много раз. Я надеюсь, что есть какой-то способ встроить призывы к foo
внутри функции оболочки, даже когда вызывается из указателя на объект:
MyClass *obj = getObject();
obj->foo(); // As I understand it, this cannot be inlined. That's okay.
obj->wrapper(); // Nor will this. However, I hope that the machine code
// for the wrapper function will contain inlined calls to
// foo().
По сути, я хочу, чтобы компилятор генерировал несколько версий функции-оболочки — по одной для каждого возможного класса — и встроенные вызовы для соответствующего foo
, что должно быть возможно, так как тип объекта определяется до выбора, который wrapper
Функция для выполнения. Это возможно? Поддерживают ли какие-либо компиляторы эту оптимизацию?
Редактировать: Я ценю все отзывы и ответы до сих пор, и я могу в конечном итоге выбрать один из них. Однако большинство ответов игнорируют последнюю часть моего вопроса, где я объясняю, почему я думаю, что эта оптимизация должна быть осуществимой. Это действительно суть моего вопроса, и я все еще надеюсь, что кто-то может ответить на этот вопрос.
Редактировать 2Я выбрал ответ Влада, поскольку он предложил популярный обходной путь и частично рассмотрел предложенную мной оптимизацию (в комментариях к ответу Дэвида). Спасибо всем, кто написал ответ — я прочитал их все, и не было явного «победителя».
Кроме того, я нашел академическую статью, в которой предлагается оптимизация, очень похожая на то, что я предлагал: http://www.ebb.org/bkuhn/articles/cpp-opt.pdf.
В некоторых случаях компилятор может определять поведение виртуальной диспетчеризации во время компиляции и выполнять вызов не виртуальной функции или даже встроенную функцию. Он может сделать это только в том случае, если выяснит, что ваш класс является «вершиной» в цепочке наследования или эти две функции не перегружены иным образом. Зачастую это просто невозможно, особенно если у вас не включена поздняя оптимизация для всей программы.
Если вы не хотите проверять результаты оптимизации вашего компилятора, вам лучше всего вообще не использовать виртуальную функцию во внутреннем цикле. Например, что-то вроде этого:
class Foo {
public:
virtual void foo()
{
foo_impl();
}
virtual void bar()
{
for (int i = 0; i < ∞; ++i) {
foo_impl();
}
}
private:
void foo_impl() { /* do some nasty stuff here */ }
};
Но в этом случае вы явно отказываетесь от мысли, что кто-то может войти, унаследовать от вашего класса и добавить собственную реализацию «foo», которую должен вызывать ваш «бар». По сути, они должны будут повторно реализовать оба.
С другой стороны, это пахнет как преждевременная оптимизация. Современные процессоры, скорее всего, будут «блокировать» ваш цикл, предсказывать выход из него и выполнять одни и те же µOP снова и снова, даже если ваш метод виртуален. Поэтому я рекомендую вам тщательно определить, что это узкое место, прежде чем тратить время на его оптимизацию.
Нет, вызов функции не будет встроенным, если выполняется через указатель или ссылку (это включает в себя this
указатель). Может быть создан новый тип, который расширяется от вашего текущего типа и переопределяет foo
без переопределения wrapper
,
Если вы хотите разрешить компилятору встроить вашу функцию, вы должны отключить виртуальную диспетчеризацию для этого вызова:
void Type::wrapper() {
Type::foo(); // no dynamic dispatch
}
Представьте себе следующую иерархию:
class Base
{
virtual void foo();
virtual void wrapper();
};
class Derived1: public Base
{
virtual void foo() { cout << "Derived1::foo"; }
virtual void wrapper() { foo(); }
};
class Derived2: public Derived1
{
virtual void foo() { cout << "Derived2::foo"; }
};
Base * p1 = new Derived1;
p1->wrapper(); // calls Derived1::wrapper which calls Derived1::foo
Base * p2 = new Derived2;
p2->wrapper(); // calls Derived1::wrapper which calls Derived2::foo
Вы видите проблему? Derived1 :: обертка должен вызовите Derived2 :: foo. До времени выполнения он не может знать, будет ли он вызывать Derived1 :: foo или Derived2 :: foo, поэтому нет способа встроить его.
Если вы хотите убедиться, что встраивание возможно, убедитесь, что функция, которую вы хотите встроить, не является виртуальной. Из вашего описания видно, что это может быть возможно, если каждый класс в иерархии переопределяет оба foo
а также wrapper
, Функция не должна быть виртуальной, чтобы быть переопределенной.
Это не точно. virtual
функции Можно быть встроенным, но только если компилятор знает статический тип объекта с уверенностью — и, таким образом, имеет гарантию, что полиморфизм работает.
Например:
struct A
{
virtual void foo()
{
}
};
struct B
{
virtual void foo()
{
}
};
int main()
{
A a;
a.foo(); //this can be inlined
A* pa = new A;
pa->foo(); //so can this
}
void goo(A* pa)
{
pa->foo() //this probably can't
}
Тем не менее, похоже, что в вашем случае это не может произойти. Что вы можете сделать, это иметь другойvirtual
функция, которая на самом деле реализует функциональность и вызывает ее статически, поэтому вызов разрешается во время компиляции:
class MyClass
{
virtual void foo() = 0;
virtual void wrapper() = 0;
};
class Derived : MyClass
{
void fooImpl()
{
//keep the actual implementation here
}
virtual void foo()
{
fooImpl();
}
virtual void wrapper()
{
for ( int i = 0 ; i < manyTimes ; i++ )
fooImpl(); //this can get inlined
}
};
или просто Derived::foo()
как указано @avakar.
Ваша функция virtual
и его тип не указан до времени выполнения, так как вы ожидаете, что компилятор встроит код некоторого класса в него?
В подобной ситуации у меня было 2 функции: foo
это виртуально и foo_impl
это нормальная функция и будет вызываться из foo
а также wrapper