在c++中禁用虚函数的动态绑定(创建虚表)

Disable dynamic binding (virtual table creation) in c++ for virtual functions

本文关键字:动态绑定 创建 虚表 函数 c++      更新时间:2023-10-16

我最近遇到了一个c++面试问题,这让我很感兴趣:

假设您错误地将某些c++成员函数声明为virtual,但是(可能出于性能原因)您希望防止编译器为该函数创建v表。也就是说,禁用三个动态函数绑定,转而使用静态绑定。

你将如何做到这一点?另外,c++ 11是否有一些特定的方法来做到这一点?

我知道没有办法强迫c++编译器禁用动态绑定,如果它支持这种选项(不是所有的c++编译器都这样做,但大多数都这样做),就不能强迫它将代码完全编译为C。然而,这有点像把婴儿连同洗澡水一起倒掉,因为它在理论上禁用了所有不属于C的c++特性。

当然,c++ 11中引入了final标识符,它可以防止从类进一步派生或覆盖虚成员。严格地说,这并不能阻止动态分派——它解决了一个不同的问题。

避免(感知或实际)动态绑定影响的一种方法是避免使用或编写任何带有虚成员函数的类,并且不要创建类层次结构(即不要从带有虚函数的类派生)。显然,如果没有虚函数,就不需要虚函数分派,因此也就不需要动态绑定。

如果你知道对象的类型,可以通过使用静态分派来避免使用动态绑定,即显式命名要调用的函数。例如,假设我们有一个类Base,它提供了一个名为foo()public virtual成员,还有一个名为Derived的类,它继承了Base并覆盖了foo()。那么下面的代码将避免执行动态分派;

 Base *b = new Derived;
 b->Base::foo();          //   static call;  will not call `Derived::foo()`
 b->Derived::Foo();       // incorrect static call.  Will not compile since b is a pointer to Base not Derived
 Derived *d = new Derived;
 d->Derived::foo();       // static call of Derived::foo()
 d->Base::foo();          // static call of Base::foo()

当然,如果使用对象的代码依赖于对象的实际类型的知识,或者依赖于被调用的foo()的特定变体,那么它的设计就会破坏具有多态基类和从它派生的其他类的目的。

在上面的例子中,编译器仍然支持虚函数调用(vtable等,如果编译器现在工作的话),这可能会影响对象的创建和销毁过程。

另一种避免动态分派(或绑定)的技术是使用模板(有时称为编译时多态性)。从本质上讲,模板可以假设一个类型提供了一些接口(或一组操作),并且可以使用具有该接口的类型的任何变量。例如,

 struct X
 {
      void foo();
 };
 template<class T> void func()
 {
      T x;          // relies on T being instantiable (and destructible)
      x.foo();      // relies on T having a member named foo()
 }
 // in some function somewhere where both X and func() are known to the compiler
 func<X>();

这样的模板不需要T类型来具有虚函数,因此不依赖于动态分派(绑定)。然而,没有什么可以阻止这样的模板函数与具有虚成员函数的类一起工作,因此这不会禁用动态绑定——它只允许程序员选择避免使用动态绑定。

如果我在面试中被问到这个问题,我可能会指出上面所有的问题,但不说这个问题相当愚蠢。熟悉c++的面试官会意识到这一点,并对你如何思考和解决这类问题感兴趣(毕竟,现实世界的开发人员经常被管理层或客户要求满足愚蠢或不切实际的需求,并且期望他们足够机智,以避免告诉他们的经理或客户他们很愚蠢)。如果面试官在问这个问题的时候没有理解这个问题(或者面试小组里没有其他理解这个问题的人),我无论如何也不想和那个雇主一起工作。

您可以通过禁用RTTI来避免开销…有一个编译时间开关。

一旦启用RTTI禁用标志,dynamic_cast/typeid的虚拟表调度就不会有任何开销。