假设我们不打算在适用的情况下将编译器提示为内联成员函数。
class Base
{
public:
inline virtual f() = 0;
};
class Derived : public Base
{
public:
virtual f() override; // no inline specifier here
};
我是否需要在inline
中指定Derived::f()
或者我可以省略关键字并确保virtual Derived::f()
与inline Derived::f()
相同?
我的意思是为inline
隐式指定的Derived::f()
关键字,还是我需要再次显式键入它?
我是否需要在
inline
中指定Derived::f()
或者我可以省略关键字并确保virtual Derived::f()
与inline Derived::f()
相同?
如果省略派生类中的inline
关键字,则派生类中的inline
不是。
我的意思是为
inline
隐式指定的Derived::f()
关键字
不它不是。
或者我需要再次明确键入它吗?
是的你是。但是,编译器很可能会为它生成代码,就好像它是一个非inline
成员函数,因为它是一个virtual
成员函数。
现代编译器试图平衡函数内联的成本和收益。
好处和成本都很清楚:当一个函数被内联时,函数调用没有开销(因为没有函数调用),并且编译器能够根据上下文对函数体进行优化在其中被调用(因为,当它被内联时,它知道上下文)。
成本可能包括增加的可执行文件大小(如果它是一个很大的功能),并且可执行程序周围的功能正文的程序集的更多实例。
一个经验法则是,如果函数很大或很复杂,它可能不会内联。如果它很小,它可能会被内联。
这很好。它可以防止膨胀的可执行文件,但仍然消除了与使用函数相关的几乎所有开销。执行大型复杂功能的时间通常使函数调用的成本相形见绌,并且内联它只会带来最小的好处。
那么inline
做什么?编译器在决定内联函数时计算函数的复杂程度。然后,它将该计算与某个阈值进行比较。如果函数不如阈值复杂,则会内联函数。
inline
关键字基本上提高了该特定函数的阈值,但它实际上在引擎盖下的作用因编译器而异。
如果编译器不知道调用了哪个函数,则无法对其进行内联。
我们来看一个例子:
// func_t is a pointer to a function that returns an integer
using func_t = int(*)();
int getNumber(func_t func) {
// The compiler can't inline func(), because it doesn't know
// what func *is*
return func();
}
虚函数调用与调用函数指针非常相似,但有一些关键差异。如果从基类调用它,编译器不会提前知道要调用的函数:
class Base {
virtual int getNum() { return 0; }
};
class Derived {
int value;
void setValue(int v) { value = v; }
int getNum() override { return value; }
};
int getNumFrom(Base& base) {
// The compiler doesn't know whether to call
// Base::getNum() or Derived::getNum(), so it can't inline it
return base.getNum();
}
但是,如果您从类的具体实例(不是引用而不是指针)调用它,则编译器确切地知道调用哪个版本:
int getNumFromDerived() {
Derived d;
// Here, we know that we're calling Derived::getNum()
// so the compiler *can* inline it.
return d.getNum();
}
您可以在基类和派生类中指定它。只知道它不能保证它们被内联,正是因为有时无法内联虚函数调用。
由于模板保留了类型信息,因此编译器始终知道要调用哪个函数。内联模板化函数调用很容易,使用它们不会增加程序的开销。
如果可能,首选模板而不是虚拟继承。