我有两个课程,我们称之为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
};
现在我正在实例化B,然后调用f1。
int main() {
B myB;
myB.f1(500);
}
我期望发生的是,它将在B中调用f1(),它继承自A,后者又将LSB设为500并将其传递给B中的f2(),这将打印出来。相反,它会在 A 中调用f1()。
为什么这不起作用?编译器在编译时知道myB是B类,而不是A.我不能让f1或f2成为虚拟的,原因有二:首先,因为性能原因我不能容忍任何函数调用开销,其次,使其内联一些常量参数允许编译器经常优化大量代码。我已经尝试在 A 中使f1()成为内联虚拟,但即使是-O3
,我也看到了(在一个特定的测试中,稍微多一些代码是不相关的在这个例子中)大约500字节的汇编代码与8字节相比,如果我只是做相当于以下的事情:
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
};
现在显然我可以将f1从A复制/粘贴到所有派生类,但在实际代码中,这将最多包含10个类,每个函数大约35个而不是1个,并且保持{{{一遍又一遍地使用相同的代码,以避免在很多地方复制此代码时出现令人难以置信的维护问题。
答案 0 :(得分:5)
这里有两个问题:
在A的上下文中调用f2()
实际上会调用A::f2()
- 句点。如果您需要多态行为,则必须使f2
虚拟。
您假设使f2
虚拟会增加运行时开销。实际上,如果编译器可以证明你在B上调用f1()
,那么就不会有任何开销。
此处的证明:https://godbolt.org/g/2OWPo2
作为奖励,内联关键字是不必要的。如果在声明点定义方法的主体,则该方法是隐式内联的。
此外(并且违反直觉),inline
不会导致生成内联代码。它只是警告编译器它可能不止一次看到定义。
内联是编译器将自行完成的优化。
最后,再次查看生成的程序集。您将看到编译器甚至无法发出vtable或任何vtable结构。如今,优化工具非常好。
答案 1 :(得分:3)
您无法承担虚拟功能,这实际上非常普遍。在这种情况下,您需要进行一些重新设计:
template<typename Impl>
class ABase {
public:
inline void f1(int n) { f2(n&1); }
inline void f2(int n) { static_cast<Impl*>(this)->f2(n); }
};
class AReal : public ABase<AReal> {
inline void f2(int n) { } // no-op here
};
class B : public ABase<B> {
public:
inline void f2(int n) { printf("n=%d\n",n); } // printf for debugging only
};
请注意,您仍然不能期望动态多态性,并且AReal和B现在不相关。 (如果不需要后者,可以通过向ABase添加非模板库来修复;前者无法修复,而且开销与虚拟数据相当。)