我有两个类,我们称它们为A和B,看起来像这样:
class A {
public:
inline void f1(int n) { f2(n&1); }
inline void f2(int n) { } // no-op in base class
};
class B : public A {
public:
inline void f2(int n) { printf("n=%d\n",n); } // printf for debugging only
};
Run Code Online (Sandbox Code Playgroud)
现在我实例化B,然后调用f1。
int main() {
B myB;
myB.f1(500);
}
Run Code Online (Sandbox Code Playgroud)
我期望发生的事情是,它将在B中调用f1(),它继承自A,这将转而使用LSB 500,并将其传递给B中的f2(),然后将其打印出来。相反,发生的是它在A中调用f1()。
为什么这不起作用?编译器在编译时就知道myB是B类,而不是A类。出于以下两个原因,我无法使f1或f2是虚拟的:首先,由于性能原因,我不能忍受任何函数调用开销;其次,使它与某些常量参数内联可以使编译器经常优化很多代码。我曾尝试使f1()在A中成为内联虚拟,但即使我看到(在一个特定的测试中,使用稍微更多的代码,与该示例无关),大约有500字节的汇编代码,而如果我只是这样做的话,则为8字节。等效于以下内容:-O3
class A {
public:
inline void f1(int n) { f2(n&1); }
inline void f2(int n) { } // no-op in base class
};
class B : public A {
public:
inline void f1(int n) { f2(n&1); }
inline void f2(int n) { printf("n=%d\n",n); } // printf for debugging only
};
Run Code Online (Sandbox Code Playgroud)
现在显然我可以将A中的f1复制/粘贴到所有派生类中,但是在实际代码中,最终将是大约10个类,并且每个函数大约包含35个函数而不是1个,并且保持#include相同的代码似乎是胡闹的并避免在很多地方复制此代码所带来的难以置信的维护问题。
这里有两个问题:
呼吁f2()在A的情况下实际调用A::f2()-期。如果您想要多态行为,则必须将其f2虚拟化。
您已经假定f2虚拟化会增加运行时的开销。实际上,如果编译器可以证明您正在调用f1()B,则不会有任何开销。
证明在这里:https : //godbolt.org/g/2OWPo2
另外,不需要内置关键字。如果在声明时定义方法的主体,则该方法是隐式内联的。
另外(且违反直觉)inline不会导致生成内联代码。它只是警告编译器它可能会多次看到该定义。
内联是编译器将自己进行的优化。
最后,再次查看生成的程序集。您将看到编译器甚至没有费心地发出vtable或任何vtable构造。这几天的优化程序真的很好。