在哪里指定内联关键字、基类、派生类或两者是否重要?

Does it matter where is the inline keyword is specified, base class, derived class or both?

本文关键字:是否 派生 基类 关键字 在哪里      更新时间:2023-10-16

假设我们不要在适用的情况下将编译器提示为内联成员函数。

class Base
{
public:
inline virtual f() = 0;
};
class Derived : public Base
{
public:
virtual f() override; // no inline specifier here
};

我是否需要在Derived::f()中指定inline,或者我可以省略关键字并确保virtual Derived::f()inline Derived::f()相同?

我的意思是inline关键字是为Derived::f()隐式指定的,还是我需要再次显式键入它?

我是否需要在Derived::f()中指定inline,或者我可以省略关键字并确保virtual Derived::f()inline Derived::f()相同?

如果在派生类中省略inline关键字,则不会inline派生类中

我的意思是为Derived::f()隐式指定的inline关键字

不,不是。

还是我需要再次显式键入它?

是的,你愿意。但是,编译器很可能会为其生成代码,就好像它是非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(); 
}

您应该如何应用内联关键字?

可以在基类和派生类中指定它。只要知道它不能保证它们被内联,正是因为有时不可能内联虚拟函数调用。

有其他选择吗?

由于模板保留类型信息,因此编译器始终知道要调用哪个函数。内联模板化函数调用很容易,使用它们不会增加程序的开销。

如果可能,首选模板而不是虚拟继承。