内联虚拟功能真的没有意义吗?

aJ.*_*aJ. 167 c++ virtual-functions inline

当我收到代码评论评论说虚拟功能不需要内联时,我收到了这个问题.

我认为在直接在对象上调用函数的场景中,内联虚函数可以派上用场.但是我想到的反驳论点是 - 为什么要想定义虚拟然后使用对象来调用方法呢?

最好不要使用内联虚拟功能,因为它们几乎从未扩展过吗?

我用于分析的代码片段:

class Temp
{
public:

    virtual ~Temp()
    {
    }
    virtual void myVirtualFunction() const
    {
        cout<<"Temp::myVirtualFunction"<<endl;
    }

};

class TempDerived : public Temp
{
public:

    void myVirtualFunction() const
    {
        cout<<"TempDerived::myVirtualFunction"<<endl;
    }

};

int main(void) 
{
    TempDerived aDerivedObj;
    //Compiler thinks it's safe to expand the virtual functions
    aDerivedObj.myVirtualFunction();

    //type of object Temp points to is always known;
    //does compiler still expand virtual functions?
    //I doubt compiler would be this much intelligent!
    Temp* pTemp = &aDerivedObj;
    pTemp->myVirtualFunction();

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

ya2*_*a23 144

有时可以内联虚函数.优秀的C++ faq的摘录:

"内联虚拟调用唯一可以内联的是编译器知道作为虚函数调用目标的对象的"确切类".这只有在编译器具有实际对象而不是指针时才会发生.引用一个对象.即,使用本地对象,全局/静态对象或复合内部的完全包含的对象."

  • 是的,但值得记住的是,编译器可以自由地忽略内联说明符,即使调用可以在编译时解析并且可以内联. (7认同)
  • 当我认为内联可能发生的另一种情况是当您调用该方法时,例如this-> Temp :: myVirtualFunction() - 这样的调用会跳过虚拟表分辨率并且函数应该内联而没有问题 - 为什么以及如果你'我想做的是另一个话题:) (6认同)
  • @RnR.没有必要使用'this->',只需使用限定名称即可.这种行为发生在析构函数,构造函数和一般的赋值运算符中(参见我的答案). (5认同)
  • sharptooth - 是的,但是AFAIK对于所有内联函数都是如此,而不仅仅是虚拟内联函数. (2认同)
  • void f(const Base&lhs,const Base&rhs){} ------在函数的实现中,你永远不知道lhs和rhs指向运行时的内容. (2认同)

MSa*_*ers 70

C++ 11已添加final.这改变了接受的答案:不再需要知道对象的确切类,只需知道对象至少具有声明final的函数的类类型即可:

class A { 
  virtual void foo();
};
class B : public A {
  inline virtual void foo() final { } 
};
class C : public B
{
};

void bar(B const& b) {
  A const& a = b; // Allowed, every B is an A.
  a.foo(); // Call to B::foo() can be inlined, even if b is actually a class C.
}
Run Code Online (Sandbox Code Playgroud)

  • @JeffreyFaust 没有理由不传播信息,不是吗?根据该链接,“icc”似乎可以做到这一点。 (3认同)

Ric*_*den 36

有一类虚拟功能,让它们内联仍然有意义.考虑以下情况:

class Base {
public:
  inline virtual ~Base () { }
};

class Derived1 : public Base {
  inline virtual ~Derived1 () { } // Implicitly calls Base::~Base ();
};

class Derived2 : public Derived1 {
  inline virtual ~Derived2 () { } // Implicitly calls Derived1::~Derived1 ();
};

void foo (Base * base) {
  delete base;             // Virtual call
}
Run Code Online (Sandbox Code Playgroud)

删除'base'的调用将执行虚拟调用以调用正确的派生类析构函数,此调用不会内联.但是因为每个析构函数都调用它的父析构函数(在这些情况下为空),编译器可以内联这些调用,因为它们不会虚拟地调用基类函数.

基类构造函数或任何函数集也存在相同的原则,其中派生实现也调用基类实现.

  • 人们应该知道,虽然空括号并不总是意味着析构函数什么都不做.析构函数默认 - 破坏类中的每个成员对象,因此如果基类中有一些向量,那些空括号中可能会有很多工作! (22认同)

Joh*_*itb 14

如果根本没有非内联函数存在(并且在一个实现文件中定义而不是在头部中定义),我已经看到了不发出任何v表的编译器.他们会抛出类似missing vtable-for-class-A或类似的错误,你会像我一样被困惑.

实际上,这与标准不符,但它确实发生了考虑将至少一个虚函数放在标题中(如果只是虚拟析构函数),这样编译器就可以在该位置为类发出vtable.我知道它发生在一些版本的gcc.

正如有人提到的,内联虚拟函数有时可能是一种好处,但当然,当您知道对象的动态类型时,通常会使用它,因为这是首先的全部原因virtual.

然而,编译器不能完全忽略inline.除了加速函数调用之外,它还有其他语义.类内定义的隐式内联是允许您将定义放入标题的机制:只有inline函数可以在整个程序中多次定义,而不会违反任何规则.最后,它的行为与您在整个程序中只定义一次一样,即使您将标题多次包含在链接在一起的不同文件中.


CAF*_*FxX 10

好吧,实际上虚拟函数总是可以内联,只要它们静态链接在一起:假设我们有一个Base 带有虚函数F和派生类的抽象类,Derived1并且Derived2:

class Base {
  virtual void F() = 0;
};

class Derived1 : public Base {
  virtual void F();
};

class Derived2 : public Base {
  virtual void F();
};
Run Code Online (Sandbox Code Playgroud)

一个hypotetical调用b->F();(b类型Base*)显然是虚拟的.但是你(或编译器 ......)可以像这样重写它(假设typeof是一个类似typeid函数,返回一个可以在a中使用的值switch)

switch (typeof(b)) {
  case Derived1: b->Derived1::F(); break; // static, inlineable call
  case Derived2: b->Derived2::F(); break; // static, inlineable call
  case Base:     assert(!"pure virtual function call!");
  default:       b->F(); break; // virtual call (dyn-loaded code)
}
Run Code Online (Sandbox Code Playgroud)

虽然我们仍然需要RTTI,但是typeof通过将vtable嵌入到指令流中并专门调用所有涉及的类,可以有效地内联调用.这也可以通过仅专门化几个类(例如,仅仅Derived1)来概括:

switch (typeof(b)) {
  case Derived1: b->Derived1::F(); break; // hot path
  default:       b->F(); break; // default virtual call, cold path
}
Run Code Online (Sandbox Code Playgroud)