Why bother with virtual functions in c++?
Asked Answered
S

6

7

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.

Sade answered 4/3, 2011 at 10:4 Comment(6)
Could you explain what you mean by "templating and typechecking" in Objective-C? Examples would be welcome.Datolite
In Objective C all methods are implicitly virtual, so I do not understand your question.Pneumatometer
Sorry for sentence being a bit misleading: "templating and typechecking" is meant for C++, in Objective C we use "id" keyword and then "isKindof" to determine the type and after that also cast if necessarySade
@poksi592: and that would mean that you had to modify the code to adapt to new types added to the hierarchy, wouldn't it? That can become a maintenance nightmare rather soon if you start extending the hierarchy.Proverbial
Hm, not really. I checked the type if I want for any kind of reason. Most because of control or whatsoever. But if I want call overridenn method from subclass, I just call it on object of general "id" type, meaning, I don't have to declare any method as virtual as in C++ and I certainly don't have to declare object of derived class as base class.Sade
I think a better question would be "why on Earth are there non virtual member functions in C++?"Labroid
C
5

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.

Cingulum answered 4/3, 2011 at 10:12 Comment(2)
I think this is an important point. If you're in the business of producing compile once and run most places code (typically for clients with libraries etc.) then inheritance is very useful. If however (like me) you don't and all the code is internal, and have no issues with the "tight-coupling", then templates makes more sense (IMHO).Eighteenmo
But then you need to declare the object as base class, right? Would that still work if you would declare it like derived class? If not, then by the time you declare and initiate the derived class you definitively have to know that you will use it as base class somewhere, right?Sade
B
1

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.

Bedspring answered 4/3, 2011 at 10:16 Comment(0)
A
1

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.

Australorp answered 4/3, 2011 at 10:20 Comment(0)
D
0

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.

Drusi answered 4/3, 2011 at 10:17 Comment(0)
W
0

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.

Waal answered 4/3, 2011 at 10:21 Comment(0)
P
0

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.

Pockmark answered 4/3, 2011 at 11:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.