C++ 如果在明确的情况下使用指针,编译器是否可以内联虚拟函数?

C++ 如果在明确的情况下使用指针,编译器是否可以内联虚拟函数?,c++,compiler-construction,virtual,inline,C++,Compiler Construction,Virtual,Inline,我已经读过了。但我仍然有一些疑问,没有找到答案 他们说,如果情况不是模棱两可的,编译器应该内联虚拟函数 class A { protected: int a; public: inline virtual void Func() { a = 0; } }; class B : public A { public: inline virtual void Func() { a = 1; } };

我已经读过了。但我仍然有一些疑问,没有找到答案

他们说,如果情况不是模棱两可的,编译器应该内联虚拟函数

class A
{
protected:

    int     a;
public:
    inline virtual void Func()
    {
        a = 0;
    }
};

class B : public A
{
public:
    inline virtual void Func()
    {
        a = 1;
    }
};

B   *obj = new B();

obj->Func();    //  Calls B::Func() through vtable;
obj->A::Func(); //  Inlines calls to A::Func();
obj->B::Func(); //  Inlines calls to B::Func();
然而:

只有当编译器有一个实际的对象而不是一个指向对象的指针或引用时,才会发生这种情况

那么,如果我有一个从
a
派生的
B
类(它包含一个
虚拟void doSth()
函数),并且我使用
B*
指针,而不是
a*
,该怎么办呢

B* b = new B;

b->doSth();
  • 假设
    B
    没有任何子类。很明显(在编译时)应该调用什么函数。所以可以内联。是真的吗
  • 假设
    B
    有一些子类,但这些类没有自己的
    doSth()
    函数。所以编译器应该“知道”唯一要调用的函数是
    B::doSth()
    。但我猜它不是内联的

  • B
    是否有任何派生类并不重要。在这种情况下,
    b
    指向一个
    b
    对象,因此编译器可以内联调用

    当然,在你的情况下,任何一个体面的现代编译器都可以也会这样做。如果你不使用指针,它会变得简单得多。这不是一个真正的“优化”。通过只查看
    -操作符左侧的AST节点,可以忽略虚拟调用这一事实变得很明显。但是如果使用指针,则需要跟踪指针对象的动态类型。但现代编译器能够做到这一点

    编辑:一些实验已经准备好了

    // main1.cpp
    struct A {
      virtual void f();
    };
    
    struct B : A { 
      virtual void f();
    };
    
    void g() {
      A *a = new A;
      a->f();
    
      a = new B;
      a->f();
    }
    
    // clang -O2 -S -emit-llvm -o - main1.cpp | c++filt
    // ...
    define void @g()() {
      %1 = tail call noalias i8* @operator new(unsigned int)(i32 4)
      %2 = bitcast i8* %1 to %struct.A*
      %3 = bitcast i8* %1 to i32 (...)***
      store i32 (...)** bitcast (i8** getelementptr inbounds ([3 x i8*]* @vtable for A, i32 0, i32 2) to i32 (...)**), i32 (...)*** %3, align 4
      tail call void @A::f()(%struct.A* %2)
      %4 = tail call noalias i8* @operator new(unsigned int)(i32 4)
      %5 = bitcast i8* %4 to i32 (...)***
      store i32 (...)** bitcast (i8** getelementptr inbounds ([3 x i8*]* @vtable for B, i32 0, i32 2) to i32 (...)**), i32 (...)*** %5, align 4
      %tmp = bitcast i8* %4 to %struct.B*
      tail call void @B::f()(%struct.B* %tmp)
      ret void
    }
    // ...
    

    可以看出,当
    a
    指向a
    a
    和当它指向a
    B
    时,clang直接调用
    f
    。GCC也这样做

    当vtable未被取消对调用的引用时,可以内联虚拟成员函数。这可以通过对成员函数进行显式作用域调用来实现

    class A
    {
    protected:
    
        int     a;
    public:
        inline virtual void Func()
        {
            a = 0;
        }
    };
    
    class B : public A
    {
    public:
        inline virtual void Func()
        {
            a = 1;
        }
    };
    
    B   *obj = new B();
    
    obj->Func();    //  Calls B::Func() through vtable;
    obj->A::Func(); //  Inlines calls to A::Func();
    obj->B::Func(); //  Inlines calls to B::Func();
    

    我敢打赌,这取决于编译器,您只需查看生成的程序集即可找到答案。你们用的是什么编译器?可能是重复的事实上这是一个关于编译器的问题。它的版本是
    4.4.1
    。你认为在1中内联足够聪明吗。二,。情况?真正的问题是,如果连编译器都能检测到指针的静态类型,为什么还要在堆上分配
    B
    ?为什么程序员不能这样做呢?如果B有一个子类在当前源文件中不可见呢?你是说编译器将跟踪作业并“知道”
    b
    包含实际的
    b*
    而不是一个孩子吗?马克:这正是他说的。谢谢。“看来GCC是相当聪明的。”约翰,谢谢你给我添麻烦。看来我必须停止低估现代编译器的能力;这不是我第一次感到惊讶。@马克:尝试推断指向值的静态类型非常简单,使用这种技术可以进行许多优化。是的。但我还想知道,如果编译器“看到”
    obj->Func(),它是否足够聪明,不会去看vtable