代码之家  ›  专栏  ›  技术社区  ›  aJ.

内联虚函数真的是无意义的吗?

  •  157
  • aJ.  · 技术社区  · 15 年前

    当我收到一个代码评审注释,说虚拟函数不需要内联时,我就得到了这个问题。

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

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

    我用于分析的代码段:

    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;
    }
    
    12 回复  |  直到 7 年前
        1
  •  136
  •   Wumpf ya23    8 年前

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

    “唯一一次内联虚拟调用 当编译器 知道对象的“确切类别” 哪个是虚拟的目标 函数调用。这只能发生 当编译器有实际对象时 而不是指向 物体。也就是说,无论是与本地 对象、全局/静态对象或 完全包含在 复合材料。”

        2
  •  67
  •   MSalters    10 年前

    添加了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.
    }
    
        3
  •  35
  •   Richard Corden    15 年前

    有一类虚拟函数在其中仍然有内联的意义。考虑以下情况:

    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
    }
    

    删除“base”的调用将执行虚拟调用以调用正确的派生类析构函数,此调用未内联。但是,由于每个析构函数调用它的父析构函数(在这些情况下为空),编译器可以内联 那些 调用,因为它们实际上不调用基类函数。

    对于基类构造函数或派生实现也调用基类实现的任何函数集,都存在相同的原则。

        4
  •  14
  •   Johannes Schaub - litb    15 年前

    我见过一些编译器,如果根本不存在非内联函数,它们就不会发出任何v-table(然后在一个实现文件中定义,而不是在头文件中定义)。他们会像 missing vtable-for-class-A 或者类似的事情,你会像我一样困惑。

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

    正如有人提到的,内联虚拟函数可以是一个好处 有时 ,但当然,最常使用的是 了解对象的动态类型,因为这是 virtual 首先。

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

        5
  •  10
  •   CAFxX    7 年前

    嗯,实际上 虚拟函数始终可以内联 ,只要它们静态链接在一起:假设我们有一个抽象类 Base 具有虚拟功能 F 和派生类 Derived1 Derived2 :

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

    低血压电话 b->F(); b 类型的 Base* )显然是虚拟的。但是你(或者) compiler …)可以这样重写它(假设 typeof 是一个 typeid -like函数返回一个可在 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)
    }
    

    我们仍然需要RTTI 类型 基本上,可以通过将vtable嵌入指令流并专门化所有相关类的调用来有效地内联调用。这也可以通过专门化几个类(比如, 导出1 ):

    switch (typeof(b)) {
      case Derived1: b->Derived1::F(); break; // hot path
      default:       b->F(); break; // default virtual call, cold path
    }
    
        6
  •  3
  •   sharptooth    15 年前

    内联的 真的什么都没做-这是个提示。编译器可能会忽略它,或者它可能在没有 内联的 如果它看到了实现并喜欢这个想法。如果代码清晰性受到威胁, 内联的 应该移除。

        7
  •  3
  •   tarachandverma    13 年前

    内联声明的虚函数在通过对象调用时是内联的,在通过指针或引用调用时被忽略。

        8
  •  3
  •   Chenna Reddy    11 年前

    将虚拟方法标记为内联,有助于在以下两种情况下进一步优化虚拟函数:

        9
  •  1
  •   anon    15 年前

    有了现代编译器,它不会对输入它们造成任何伤害。一些古老的编译器/链接器组合可能已经创建了多个vtables,但我不认为这是一个问题了。

        10
  •  1
  •   moonshadow    15 年前

    如果函数调用是明确的,并且函数是一个合适的内联候选函数,那么编译器就足够聪明,可以无论如何内联代码。

    剩下的时间“inline-virtual”是胡说八道,实际上有些编译器不会编译这些代码。

        11
  •  0
  •   Jonathan Leffler    15 年前

    编译器只能在编译时明确解析调用时内联函数。

    但是,虚拟函数是在运行时解析的,因此编译器无法内联调用,因为在编译类型时无法确定动态类型(因此也无法确定要调用的函数实现)。

        12
  •  0
  •   Balthazar    12 年前

    创建虚拟函数,然后在对象上调用它们,而不是在引用或指针上调用它们,这是有意义的。Scott Meyer在他的《有效C++》一书中建议,永远不要重新定义继承的非虚函数。这是有道理的,因为当你用一个非虚函数生成一个类并在派生类中重新定义该函数时,你可能会确保自己正确地使用它,但是你不能确保其他人会正确地使用它。另外,你以后可能会错误地使用它。所以,如果您在基类中创建一个函数,并且希望它是可重定的,那么您应该使它成为虚拟的。如果创建虚拟函数并在对象上调用它们是有意义的,那么内联它们也是有意义的。