调用一个非虚函数的成本是多少?

我有一个纯粹的抽象基础和两个派生类:

struct B { virtual void foo() = 0; };
struct D1 : B { void foo() override { cout << "D1::foo()" << endl; } };
struct D2 : B { void foo() override { cout << "D1::foo()" << endl; } };

在A点调用foo的花费与对非虚拟成员函数的调用相同吗? 还是比D1和D2不会来自B更昂贵?

int main() {
 D1 d1; D2 d2; 
 std::vector<B*> v = { &d1, &d2 };

 d1.foo(); d2.foo(); // Point A (polymorphism not necessary)
 for(auto&& i : v) i->foo(); // Polymorphism necessary.

 return 0;
}

答案: Andy Prowl的答案是正确的答案,我只想添加gcc的汇编输出(在godbolt中测试:gcc-4.7 -O2 -march = native -std = c ++ 11)。 直接函数调用的代价是:

mov rdi, rsp
call    D1::foo()
mov rdi, rbp
call    D2::foo()

对于多态的调用:

mov rdi, QWORD PTR [rbx]
mov rax, QWORD PTR [rdi]
call    [QWORD PTR [rax]]
mov rdi, QWORD PTR [rbx+8]
mov rax, QWORD PTR [rdi]
call    [QWORD PTR [rax]]

但是,如果对象不是从B派生的,而只是执行直接调用,则gcc将内联函数调用:

mov esi, OFFSET FLAT:.LC0
mov edi, OFFSET FLAT:std::cout
call    std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)

如果D1D2不是从B派生出来的话,这可能会进一步优化,所以我猜测它们不是等价的 (至少对于具有这些优化的这个版本的gcc,-O3产生了没有内联的类似输出)。 在D1D2确实来自B的情况下,有没有什么能够防止编译器内联?

“修复”:使用委托(自己又称为重新实现虚拟功能):

struct DG { // Delegate
 std::function<void(void)> foo;
 template<class C> DG(C&& c) { foo = [&](void){c.foo();}; }
};

然后创建委托人的向量:

std::vector<DG> v = { d1, d2 };

这允许内联,如果你以非多态的方式访问方法。 不过,我猜测访问矢量将会比使用虚拟函数(不能用godbolt测试)慢(或者至少一样快,因为std::function使用虚拟函数进行类型擦除)。


在A点调用foo的花费与对非虚拟成员函数的调用相同吗?

是。

还是比D1和D2不会来自B更昂贵?

没有。

编译器会静态地解析这些函数调用,因为它们不是通过指针或引用来执行的。 由于调用函数的对象的类型在编译时已知,因此编译器知道必须调用foo()哪个实现。


最简单的解决方案是查看编译器内部。 在锵我们发现canDevirtualizeMemberFunctionCall中的lib /的CodeGen / CGClass.cpp:

/// canDevirtualizeMemberFunctionCall - Checks whether the given virtual member
/// function call on the given expr can be devirtualized.
static bool canDevirtualizeMemberFunctionCall(const Expr *Base, 
                                              const CXXMethodDecl *MD) {
  // If the most derived class is marked final, we know that no subclass can
  // override this member function and so we can devirtualize it. For example:
  //
  // struct A { virtual void f(); }
  // struct B final : A { };
  //
  // void f(B *b) {
  //   b->f();
  // }
  //
  const CXXRecordDecl *MostDerivedClassDecl = getMostDerivedClassDecl(Base);
  if (MostDerivedClassDecl->hasAttr<FinalAttr>())
    return true;

  // If the member function is marked 'final', we know that it can't be
  // overridden and can therefore devirtualize it.
  if (MD->hasAttr<FinalAttr>())
    return true;

  // Similarly, if the class itself is marked 'final' it can't be overridden
  // and we can therefore devirtualize the member function call.
  if (MD->getParent()->hasAttr<FinalAttr>())
    return true;

  Base = skipNoOpCastsAndParens(Base);
  if (const DeclRefExpr *DRE = dyn_cast<DeclRefExpr>(Base)) {
    if (const VarDecl *VD = dyn_cast<VarDecl>(DRE->getDecl())) {
      // This is a record decl. We know the type and can devirtualize it.
      return VD->getType()->isRecordType();
    }

    return false;
  }

  // We can always devirtualize calls on temporary object expressions.
  if (isa<CXXConstructExpr>(Base))
    return true;

  // And calls on bound temporaries.
  if (isa<CXXBindTemporaryExpr>(Base))
    return true;

  // Check if this is a call expr that returns a record type.
  if (const CallExpr *CE = dyn_cast<CallExpr>(Base))
    return CE->getCallReturnType()->isRecordType();

  // We can't devirtualize the call.
  return false;
}

我相信代码(和附带的评论)是不言自明的:)

链接地址: http://www.djcxy.com/p/54267.html

上一篇: Whats the cost of calling a virtual function in a non

下一篇: What is an interface in Java?