为什么要为 c++ 中的虚拟函数烦恼?

发布于 2024-10-20 04:06:42 字数 334 浏览 5 评论 0原文

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

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

对于愚蠢的问题,抱歉,但我还没有用 C++ 做过任何真正的项目,因为我是活跃的 Objective C 开发人员(它是一种较小的语言,因此严重依赖 SDK 的功能,如 OSX、iOS),我需要对任何并行有清晰的了解两个堂兄弟的方式。

This is not a question about how they work and declared, this I think is pretty much clear to me. The question is about why to implement this?
I suppose the practical reason is to simplify bunch of other code to relate and declare their variables of base type, to handle objects and their specific methods from many other subclasses?

Could this be done by templating and typechecking, like I do it in Objective C? If so, what is more efficient? I find it confusing to declare object as one class and instantiate it as another, even if it is its child.

SOrry for stupid questions, but I havent done any real projects in C++ yet and since I am active Objective C developer (it is much smaller language thus relying heavily on SDK's functionalities, like OSX, iOS) I need to have clear view on any parallel ways of both cousins.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

寄风 2024-10-27 04:06:42

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

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

Yes, this can be done with templates, but then the caller must know what the actual type of the object is (the concrete class) and this increases coupling.

With virtual functions the caller doesn't need to know the actual class - it operates through a pointer to a base class, so you can compile the client once and the implementor can change the actual implementation as much as it wants and the client doesn't have to know about that as long as the interface is unchanged.

相守太难 2024-10-27 04:06:42

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

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

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

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

Virtual functions implement polymorphism. I don't know Obj-C, so I cannot compare both, but the motivating use case is that you can use derived objects in place of base objects and the code will work. If you have a compiled and working function foo that operates on a reference to base you need not modify it to have it work with an instance of derived.

You could do that (assuming that you had runtime type information) by obtaining the real type of the argument and then dispatching directly to the appropriate function with a switch of shorts, but that would require either manually modifying the switch for each new type (high maintenance cost) or having reflection (unavailable in C++) to obtain the method pointer. Even then, after obtaining a method pointer you would have to call it, which is as expensive as the virtual call.

As to the cost associated to a virtual call, basically (in all implementations with a virtual method table) a call to a virtual function foo applied on object o: o.foo() is translated to o.vptr[ 3 ](), where 3 is the position of foo in the virtual table, and that is a compile time constant. This basically is a double indirection:

From the object o obtain the pointer to the vtable, index that table to obtain the pointer to the function and then call. The extra cost compared with a direct non-polymorphic call is just the table lookup. (In fact there can be other hidden costs when using multiple inheritance, as the implicit this pointer might have to be shifted), but the cost of the virtual dispatch is very small.

相对绾红妆 2024-10-27 04:06:42

我不知道关于 Objective-C 的第一件事,但这就是为什么你想“将一个对象声明为一个类并将其实例化为另一个类”:里氏替换原理

由于 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 函数实现,或者使用粗略的 hack 实现重新发明了虚拟轮子。程序需要知道在运行时要应用各种print方法中的哪一个。

模板化解决了一个不同的问题,您可以在编译时确定要使用哪些容器(例如,当您想要存储一堆元素时)。如果你使用模板函数来操作这些容器,那么当你切换容器或者在程序中添加另一个容器时,就不需要重写它们。

I don't know the first thing about Objective-C, but here's why you want to "declare an object as one class and instantiate it as another": the Liskov Substitution Principle.

Since a PDF is a document, and an OpenOffice.org document is a document, and a Word Document is a document, it's quite natural to write

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();

Now, for this to work, print would have to be virtual, or be implemented using virtual functions, or be implemented using a crude hack that reinvents the virtual wheel. The program need to know at runtime which of various print methods to apply.

Templating solves a different problem, where you determine at compile time which of the various containers you're going to use (for example) when you want to store a bunch of elements. If you operate on those containers with template functions, then you don't need to rewrite them when you switch containers, or add another container to your program.

悟红尘 2024-10-27 04:06:42

虚函数在继承中很重要。考虑一个示例,其中您有一个 CMonster 类,然后有一个从 CMonster 继承的 CRaidBoss 和 CBoss 类。

两者都需要绘制。 CMonster 有一个 Draw() 函数,但 CRaidBoss 和 CBoss 的绘制方式不同。因此,由他们利用虚函数Draw来实现。

A virtual function is important in inheritance. Think of an example where you have a CMonster class and then a CRaidBoss and CBoss class that inherit from CMonster.

Both need to be drawn. A CMonster has a Draw() function, but the way a CRaidBoss and a CBoss are drawn is different. Thus, the implementation is left to them by utilizing the virtual function Draw.

反差帅 2024-10-27 04:06:42

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

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

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

如果您没有虚函数机制,则必须编写:

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. 每个子班都有一个额外的 if 。
  3. 每次向整个层次结构添加定义时,您都必须再次触摸此功能。
  4. 它是可见代码,每次都很容易出错。

因此,将函数标记为虚拟会以隐式方式正确执行此操作,以动态方式自动重新路由,该函数调用正确的实现,具体取决于对象的最终类型。
您不必编写任何逻辑,因此您不会在此代码中出现错误,并且无需担心其他事情。

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

Well, the idea is simply to allow the compiler to perform checks for you.

It's like a lot of features : ways to hide what you don't want to have to do yourself. That's abstraction.

Inheritance, interfaces, etc. allow you to provide an interface to the compiler for the implementation code to match.

If you didn't have the virtual function mecanism, you would have to write :

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();
    }
}

Here there are several problems :

  1. you have to write this for each function redefined in a class hierarchy.
  2. you have one additional if for each child class.
  3. you have to touch this function again each time you add a definition to the whole hierarcy.
  4. it's visible code, you can get it wrong easily, each time

So, marking functions virtual does this correctly in an implicit way, rerouting automatically, in a dynamic way, the function call to the correct implementation, depending on the final type of the object.
You dont' have to write any logic so you can't get errors in this code and have an additional thing to worry about.

It's the kind of thing you don't want to bother with as it can be done by the compiler/runtime.

秋日私语 2024-10-27 04:06:42

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

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

C++ 有非常强大的模板。使用模板可以使代码更快,尽管每次使用模板都会为给定类型实例化它。这意味着,如果您对整数、双精度和字符串使用 std::vector,您将拥有三个不同的向量类:这意味着可执行文件的大小将受到影响。

The use of templates is also technically known as polymorphism from theorists. Yep, both are valid approach to the problem. The implementation technics employed will explain better or worse performance for them.

For example, Java implements templates, but through template erasure. This means that it is only apparently using templates, under the surface is plain old polymorphism.

C++ has very powerful templates. The use of templates makes code quicker, though each use of a template instantiates it for the given type. This means that, if you use an std::vector for ints, doubles and strings, you'll have three different vector classes: this means that the size of the executable will suffer.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文