为什么要在c ++中使用虚函数呢?

时间:2011-03-04 10:04:42

标签: c++ objective-c polymorphism virtual-functions

这不是关于它们如何工作和宣布的问题,我认为这对我来说非常清楚。问题是为什么要实现这个? 我认为实际的原因是为了简化其他代码的关联并声明它们的基类型变量,从许多其他子类处理对象及其特定方法?

这可以通过模板化和类型检查完成,就像我在Objective C中那样做吗?如果是这样,什么更有效?我发现将对象声明为一个类并将其实例化为另一个类是困惑的,即使它是它的孩子。

SOrry愚蠢的问题,但我还没有用C ++完成任何真正的项目,因为我是活跃的Objective C开发人员(它是一个小得多的语言,因此很大程度上依赖于SDK的功能,如OSX,iOS)我需要有清晰的视图两个表兄弟的任何平行方式。

6 个答案:

答案 0 :(得分:5)

是的,这可以使用模板完成,但是调用者必须知道对象的实际类型(具体类),这会增加耦合。

使用虚函数,调用者不需要知道实际的类 - 它通过指向基类的指针进行操作,因此您可以编译客户端一次,实现者可以根据需要更改实际实现,并且只要接口没有改变,客户端就不必知道它。

答案 1 :(得分:1)

我不知道关于Objective-C的第一件事,但是这就是为什么你要“将一个对象声明为一个类并将其实例化为另一个”:Liskov Substitution Principle

由于PDF 文档,而OpenOffice.org文档文档,而Word文档文档,因此很自然地写

Document *d;
if (ends_with(filename, ".pdf"))
    d = new PdfDocument(filename);
else if (ends_with(filename, ".doc"))
    d = new WordDocument(filename);
else
    // you get the point
d->print();

现在,为了实现这一点,print必须是virtual,或者使用virtual函数实现,或者使用重新发布{{1}的粗糙黑客实现轮子。该程序需要知道在运行时要应用的各种virtual方法中的哪一种。

模板解决了一个不同的问题,在编译时确定 当你想要存储一堆元素时,你要使用的各种容器(例如)。如果您使用模板函数对这些容器进行操作,则在切换容器或向程序中添加另一个容器时无需重写它们。

答案 2 :(得分:1)

虚函数实现多态。我不知道Obj-C,所以我无法比较两者,但激励用例是你可以使用派生对象代替基础对象,代码将起作用。如果您有一个编译和工作函数foo,它对base的引用进行操作,则无需修改它以使其与derived的实例一起使用。

你可以这样做(假设你有运行时类型信息),方法是获取参数的真实类型,然后通过short开关直接调度到相应的函数,但这需要手动修改每个新的开关。类型(高维护成本)或具有反射(在C ++中不可用)以获取方法指针。即使这样,在获得方法指针后,您也必须调用它,这与虚拟调用一样昂贵。

关于虚拟调用的相关成本,基本上(在所有虚拟方法表的实现中)对对象foo应用的虚拟函数o的调用是:o.foo()转换为o.vptr[ 3 ](),其中3foo在虚拟表中的位置,这是一个编译时常量。这基本上是双重间接的:

从对象o获取指向vtable的指针,索引该表以获取指向该函数的指针然后调用。与直接非多态调用相比的额外成本只是表查找。 (实际上,在使用多重继承时可能存在其他隐藏成本,因为可能必须移动隐式this指针),但虚拟调度的成本非常小。

答案 3 :(得分:0)

虚拟功能在继承中很重要。想想一个例子,你有一个CMonster类,然后是一个继承自CMonster的CRaidBoss和CBoss类。

两者都需要绘制。 CMonster具有Draw()函数,但绘制CRaidBoss和CBoss的方式不同。因此,通过利用虚拟函数Draw来实现它们。

答案 4 :(得分:0)

嗯,这个想法只是让编译器为你执行检查。

这就像很多功能:隐藏你不想自己做的事情的方法。这是抽象。

继承,接口等允许您为编译器提供一个接口,以便匹配实现代码。

如果你没有虚函数机制,你必须写:

class A
{
    void do_something();   
};

class B : public A
{
    void do_something(); // this one "hide" the A::do_something(), it replace it.
};


void DoSomething( A* object )
{
    // calling object->do_something will ALWAYS call A::do_something()
    // that's not what you want if object is B...
    // so we have to check manually:

    B* b_object = dynamic_cast<B*>( object );

    if( b_object != NULL ) // ok it's a b object, call B::do_something();
    {
        b_object->do_something()
    }
    else
    {
        object->do_something(); // that's a A, call A::do_something();
    }
}

这里有几个问题:

  1. 您必须为在类层次结构中重新定义的每个函数编写此函数。
  2. 每个儿童班都有一个额外的。
  3. 每次向整个层级添加定义时,都必须再次触摸此功能。
  4. 它是可见的代码,每次都可以轻松搞定错误
  5. 因此,标记函数virtual以隐式方式正确,以动态方式自动重新路由,函数调用正确的实现,具体取决于最终对象的类型。 你不必写任何逻辑,所以你不能在这段代码中得到错误,还有一件事需要担心。

    这是你不想打扰的事情,因为它可以由编译器/运行时完成。

答案 5 :(得分:0)

模板的使用在技术上也被称为理论家的多态性。是的,两者都是解决问题的有效方法。所采用的实施技术将为他们解释更好或更差的表现。

例如,Java实现模板,但是通过模板擦除。这意味着它只是显然使用模板,表面下是普通的旧多态。

C ++有非常强大的模板。模板的使用使代码更快,尽管每次使用模板为给定类型实例化。这意味着,如果你对int,double和string使用std :: vector,你将有三个不同的vector类:这意味着可执行文件的大小将受到影响。