【问题标题】:difference in using virtual and not using virtual [duplicate]使用虚拟和不使用虚拟的区别[重复]
【发布时间】:2011-03-03 07:24:54
【问题描述】:

可能重复:
Overriding vs Virtual

在C++中,无论你是否选择使用virtual,你仍然可以重写基类函数。下面的编译就好了...

class Enemy 
{
public:
    void SelectAnimation();
    void RunAI();
    void Interact()
    {
        cout<<"Hi I am a regular Enemy";
    }

private:
    int m_iHitPoints;
};

class Boss : public Enemy
{
public:
    void Interact()
    {
        cout<<"Hi I am a evil Boss";
    }
};

所以我的问题是使用或不使用虚函数有什么区别。还有什么缺点。

【问题讨论】:

  • 技术上的答案是:非虚拟方法受编译时限制,虚拟方法在运行时得到解析。
  • 只要您不需要额外的性能,我建议将所有方法设为虚拟。使用虚方法子类化很多简单。
  • 删除了 C 标签,因为这是 C++。

标签: visual-c++


【解决方案1】:

如果你有代码:

Enemy * p = new Boss;
p->Interact();

Interact 不是虚拟的,你会得到 Enemy's Interact。换句话说,函数将根据被调用的事物的表面类型而不是其真实类型来选择。这几乎不是您想要的,因此如果您打算通过基指针调用方法(例如,如果您在向量或列表中有一组基指针),那么该函数应该在基类中设置为虚拟。您还需要将此类基类的析构函数设为虚拟,以便通过基指针删除实例时的行为得到明确定义。

【讨论】:

  • 这很有意义。因此,如果您使用的程序严重依赖将类转换为基类,它将无法正常工作。
  • @numerical25:这就是所谓的多态。如果你需要多态行为,你需要virtual
  • 那么,为什么不对每个函数使用virtual
  • @Omnifarious:virtual 在内存和函数调用开销方面都有一个小的间接损失。在大多数实现中,它归结为对小型调用表的额外指针取消引用。在某些情况下,该开销可能是不可取的或令人望而却步的,因此如果您不需要它,则没有理由必须支付该开销罚款。
  • @Let_Me_Be:这不是我说的吗?
【解决方案2】:

如果Enemy::Interact() 未声明virtual,则从基类中的成员函数或通过对基类的指针或引用调用Enemy::Interact() 将不会调用派生类Interact() 函数。

例如:

Boss boss;
Enemy* bossEnemy = &boss;

boss.Interact();       // calls Boss::Interact()
bossEnemy->Interact(); // calls Enemy::Interact()

如果您将Enemy::Interact() 声明为virtual,那么Boss::Interact() 将按照您的预期调用。

使用虚函数的缺点是调用它们可能比非虚函数更昂贵。不使用虚函数的缺点是你很可能得不到你想要的结果。

【讨论】:

  • 调用虚拟函数总是比非虚拟函数更昂贵,因为编译器无法内联和优化它们。例外情况是类型是静态已知的,例如,如果 var 是堆栈分配并直接访问的。
【解决方案3】:

您使用游戏开发标记了该问题,在这种情况下,可能会草率地忽略虚拟呼叫的额外呼叫开销:Elan Ruskin measured 50% increase 呼叫开销。同一个人(和许多其他游戏开发者)consider it a good practice 仅在您有具体原因时才使用虚拟功能的附加灵活性,而不仅仅是为了好玩。

这里是 technical writeup 额外费用的原因,以及一些关于 虚函数的额外费用的思考。

【讨论】:

  • +1 表示平衡良好。顺便说一句,Elan Ruskin(又名 Crashworks)的帖子的重点是 vptr 查找不是问题 - 阻碍的是指令调度
  • 非常正确。事实上,Maciej Sinilo 确实尝试隔离 vptr 查找成本,并发现它是不可测量的:msinilo.pl/blog/?p=401
【解决方案4】:

多态性。多态性。多态性。 :)

virtual 功能使 C++ 面向对象。这是您首先使用 C++ 的主要原因之一。 如果您的设计需要使用virtual,请不要三思而后行。不要仅仅为了避免虚拟而重新设计您的模型。

即使从结构的基础跳转到内存偏移会增加成本,您是否会三思而后行访问结构字段?不,如果设计需要,您当然不会。您是否会三思而后行地传递回调、事件侦听器、函子或任何其他需要跳转才能到达实际数据的“逻辑”地址?如果设计需要,你当然不会。

另一方面,如果设计不需要它,则将类成员设为虚拟是没有意义的,就像如果设计不需要它,则无需传递函子或不必要地创建结构。 决定是否使用 virtual 是良好 OO 设计和实施的一部分。

性能

关于所谓的性能成本:首先,这是一个非常古老的问题。虚拟调用的早期 C++ 实现的性能实际上可以在没有令人难以置信的人为代码的情况下进行测量。正如其他人所提到的,今天的技术在很大程度上已经淘汰了这场辩论。

第二,向量乘法和类似人为设计的例子具有误导性。他们似乎在衡量虚拟呼叫和非虚拟呼叫之间的差异。但他们不是。他们正在测量 十亿 次虚拟调用和 十亿 次对几乎什么都不做的函数的非虚拟调用之间的差异。是否存在可能易受此问题影响的实际代码?这当然是可能的。当您找到它时,解决方案是否会成为一般使用虚拟的替罪羊?显然不是。解决方案是优化您对性能异常敏感的代码。作为这种假设优化的一部分,删除虚拟是明智的,但不会给你带来太多好处。如果您的代码对性能很敏感,那么您需要优化的不仅仅是丢弃虚拟对象。

第三,它很容易衡量,这很好,因为您不需要相信我们的话。您可以轻松地在目标架构上使用编译器对差异进行基准测试,以确保确实没有性能差异

【讨论】:

  • fwiw,在普通的 mac mini 上,在愚蠢的向量乘法代码中,虚拟的开销是 0.6 纳秒。
猜你喜欢
  • 2019-01-01
  • 2016-04-16
  • 2013-10-01
  • 2021-03-26
  • 2016-01-09
  • 1970-01-01
  • 2021-09-11
  • 2014-07-14
  • 1970-01-01
相关资源
最近更新 更多