我已经读过内联虚函数真的是无意义的吗?。但我仍然有一些疑问,并没有找到答案。
他们说 如果情况明确,编译器应该内联虚拟函数。
但是:
仅当编译器具有实际对象而不是对象的指针或引用时,才会发生这种情况。
那么,如果我有一个从
B
派生的 A
类(包含 virtual void doSth()
函数)并且我使用 B*
指针,而不是 A*
:
B* b = new B;
b->doSth();
B
没有任何子类。应该调用什么函数(在编译时)是相当明显的。所以内联是可以的。事实是这样吗?B
有一些子类,但这些类没有自己的 doSth()
函数。因此编译器应该“知道”唯一要调用的函数是B::doSth()
。我猜它不是内联的?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
}
// ...
可以看出,当
f
指向 a
和指向 A
时,clang 都会直接调用 B
。海湾合作委员会也这样做。
当 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();