Ответ 1
Не имеет значения, имеет ли B
любые производные классы. В этой ситуации B
указывает на объект B
, чтобы компилятор мог встроить вызов.
И, конечно, любой достойный современный компилятор может и будет делать это в вашей ситуации. Если вы не используете указатели, это становится намного проще. Тогда это не "оптимизация". Тот факт, что вы можете опустить виртуальный вызов, становится очевидным, если посмотреть только на AST node в левой части .
-оператора. Но если вы используете указатели, вам нужно отслеживать динамический тип получателя. Но современные компиляторы способны на это.
EDIT: некоторый эксперимент в порядке.
// 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 a
и когда он указывает на a B
. GCC делает это тоже.