Может ли компилятор встроить виртуальную функцию, если я использую указатель в ясной ситуации? - PullRequest
10 голосов
/ 27 июля 2011

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

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

Тем не менее:

Это может произойти, только когда компилятор имеет фактический объект, а не указатель или ссылку на объект.

Так что, если у меня есть класс B, полученный из класса A (который содержит функцию virtual void doSth()), и я использую указатель B*, а не A*:

B* b = new B;

b->doSth();
  1. Предположим, что у B нет дочерних классов. Это довольно очевидно (во время компиляции), какую функцию следует вызывать. Так что можно быть в курсе. Это на самом деле?
  2. Предположим, что у B есть некоторые дочерние классы, но у этих классов нет собственной функции doSth(). Таким образом, компилятор должен «знать», что единственная функция для вызова - B::doSth(). Я думаю, что это не встроенный хотя?

Ответы [ 2 ]

14 голосов
/ 27 июля 2011

Не имеет значения, имеет ли B какие-либо производные классы. В этой ситуации b указывает на объект B, поэтому компилятор может встроить вызов.

И, конечно, любой достойный современный компилятор может и сделает это в вашей ситуации. Если вы не используете указатели, это становится намного проще. Это не совсем «оптимизация» тогда. Тот факт, что вы можете опустить виртуальный вызов, становится очевидным, если взглянуть только на узел AST слева от оператора .. Но если вы используете указатели, вам нужно отслеживать динамический тип указателя. Но современные компиляторы способны на это.

РЕДАКТИРОВАТЬ: Некоторые эксперименты в порядке.

// main1.cpp
struct A {
  virtual void f();
};

struct B : A { 
  virtual void f();
};

void g() {
  A *a = new A;
  a->f();

  a = new B;
  a->f();
}

// clang -O2 -S -emit-llvm -o - main1.cpp | c++filt
// ...
define void @g()() {
  %1 = tail call noalias i8* @operator new(unsigned int)(i32 4)
  %2 = bitcast i8* %1 to %struct.A*
  %3 = bitcast i8* %1 to i32 (...)***
  store i32 (...)** bitcast (i8** getelementptr inbounds ([3 x i8*]* @vtable for A, i32 0, i32 2) to i32 (...)**), i32 (...)*** %3, align 4
  tail call void @A::f()(%struct.A* %2)
  %4 = tail call noalias i8* @operator new(unsigned int)(i32 4)
  %5 = bitcast i8* %4 to i32 (...)***
  store i32 (...)** bitcast (i8** getelementptr inbounds ([3 x i8*]* @vtable for B, i32 0, i32 2) to i32 (...)**), i32 (...)*** %5, align 4
  %tmp = bitcast i8* %4 to %struct.B*
  tail call void @B::f()(%struct.B* %tmp)
  ret void
}
// ...

Как видно, clang делает прямые звонки на f, когда a указывает на A и когда он указывает на B. GCC делает это тоже.

1 голос
/ 27 июля 2011

Виртуальная функция-член может быть встроена, если vtable не разыменован для вызова.Это можно сделать, вызвав явно ограниченный вызов функции-члена.

class A
{
protected:

    int     a;
public:
    inline virtual void Func()
    {
        a = 0;
    }
};

class B : public A
{
public:
    inline virtual void Func()
    {
        a = 1;
    }
};

B   *obj = new B();

obj->Func();    //  Calls B::Func() through vtable;
obj->A::Func(); //  Inlines calls to A::Func();
obj->B::Func(); //  Inlines calls to B::Func();
...