c++optimizationinlinevirtual

Can a compiler inline a virtual function if I use a pointer in a clear situation?


I've already read Are inline virtual functions really a non-sense?. But I still have some doubts and found no answer there.

They say that if situation isn't ambiguous, compiler should inline the virtual function.

However:

This can happen only when the compiler has an actual object rather than a pointer or reference to an object.

So what if I have a B class derived from an A one (which contains a virtual void doSth() function) and I use the B* pointer, not the A*:

B* b = new B;

b->doSth();
  1. Suppose that the B hasn't any child classes. It's rather obvious (on the compile time) what function should be called. So it's possible to be inlined. Is it in fact?
  2. Suppose that the B has some child classes but these classes haven't its own doSth() function. So compiler should "know" that the only function to call is B::doSth(). I guess it doesn't inline though?

Solution

  • It doesn't matter whether B has any derived classes. In that situation b points to a B object so the compiler can inline the call.

    And surely any decent modern compiler can and will do that in your situation. If you don't use pointers it becomes a whole lot easier. It's not really an "optimization" then. The fact that you can omit a virtual call then becomes obvious by only looking at the AST node at the left side of the .-operator. But if you use pointers, you need to track the dynamic type of the pointee. But modern compilers are capable of that.

    EDIT: Some experiment is in order.

    // 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
    }
    // ...
    

    As can be seen, clang does direct calls to f, both when a points to a A and when it points to a B. GCC does that too.