Trouble understanding C++ `virtual`
Asked Answered
R

9

5

I'm having trouble understanding what the purpose of the virtual keyword in C++. I know C and Java very well but I'm new to C++

From wikipedia

In object-oriented programming, a virtual function or virtual method is a function or method whose behavior can be overridden within an inheriting class by a function with the same signature.

However I can override a method as seen below without using the virtual keyword

#include <iostream>

using namespace std;

class A {
    public:
        int a();
};

int A::a() {
    return 1;   
}

class B : A { 
    public:
        int a();
};

int B::a() {
    return 2;
}

int main() {
    B b;
    cout << b.a() << endl;
    return 0;
}

//output: 2

As you can see below, the function A::a is successfully overridden with B::a without requiring virtual

Compounding my confusion is this statement about virtual destructors, also from wikipedia

as illustrated in the following example, it is important for a C++ base class to have a virtual destructor to ensure that the destructor from the most derived class will always be called.

So virtual also tells the compiler to call up the parent's destructors? This seems to be very different from my original understanding of virtual as "make the function overridable"

Ragi answered 1/12, 2009 at 21:21 Comment(0)
T
16

Make the following changes and you will see why:

#include <iostream>

using namespace std;

class A {
    public:
        int a();
};

int A::a() {
    return 1;   
}

class B : public A { // Notice public added here
    public:
        int a();
};

int B::a() {
    return 2;
}

int main() {
    A* b = new B(); // Notice we are using a base class pointer here
    cout << b->a() << endl; // This will print 1 instead of 2
    delete b; // Added delete to free b
    return 0;
}

Now, to make it work like you intended:

#include <iostream>

using namespace std;

class A {
    public:
        virtual int a(); // Notice virtual added here
};

int A::a() {
    return 1;   
}

class B : public A { // Notice public added here
    public:
        virtual int a(); // Notice virtual added here, but not necessary in C++
};

int B::a() {
    return 2;
}

int main() {
    A* b = new B(); // Notice we are using a base class pointer here
    cout << b->a() << endl; // This will print 2 as intended
    delete b; // Added delete to free b
    return 0;
}

The note that you've included about virtual destructors is exactly right. In your sample there is nothing that needs to be cleaned-up, but say that both A and B had destructors. If they aren't marked virtual, which one is going to get called with the base class pointer? Hint: It will work exactly the same as the a() method did when it was not marked virtual.

Tammy answered 1/12, 2009 at 21:26 Comment(2)
No need to complicate the example with new/delete: B obj; A* p = &obj; or A& ref = obj;Regurgitate
True - I actually left out the delete's in my first edits; but dereferencing semantics in C++ are complicated too... ;-)Tammy
B
9

You could think of it as follows.

All functions in Java are virtual. If you have a class with a function, and you override that function in a derived class, it will be called, no matter the declared type of the variable you use to call it.

In C++, on the other hand, it won't necessarily be called.

If you have a base class Base and a derived class Derived, and they both have a non-virtual function in them named 'foo', then

Base * base;
Derived *derived;

base->foo(); // calls Base::foo
derived->foo(); // calls Derived::foo

If foo is virtual, then both call Derived::foo.

Burch answered 1/12, 2009 at 21:27 Comment(0)
K
2

virtual means that the actual method is determined runtime based on what class was instantiated not what type you used to declare your variable. In your case this is a static override it will go for the method defined for class B no matter what was the actual type of the object created

Kepner answered 1/12, 2009 at 21:28 Comment(0)
T
2

So virtual also tells the compiler to call up the parent's destructors? This seems to be very different from my original understanding of virtual as "make the function overridable"

Your original and your new understanding are both wrong.

  • Methods (you call them functions) are always overridable. No matter if virtual, pure, nonvirtual or something.
  • Parent destructors are always called. As are the constructors.

"Virtual" does only make a difference if you call a method trough a pointer of type pointer-to-baseclass. Since in your example you don't use pointers at all, virtual doesn't make a difference at all.

If you use a variable a of type pointer-to-A, that is A* a;, you can not only assign other variables of type pointer-to-A to it, but also variables of type pointer-to-B, because B is derived from A.

A* a; 
B* b;

b = new B(); // create a object of type B. 
a = b;       // this is valid code. a has still the type pointer-to-A, 
             // but the value it holds is b, a pointer to a B object.

a.a();       // now here is the difference. If a() is non-virtual, A::a()
             // will be called, because a is of type pointer-to-A. 
             // Whether the object it points to is of type A, B or
             // something entirely different doesn't matter, what gets called
             // is determined during compile time from the type of a.

a.a();       // now if a() is virtual, B::a() will be called, the compiler
             // looks during runtime at the value of a, sees that it points
             // to a B object and uses B::a(). What gets called is determined
             // from the type of the __value__ of a.
Th answered 1/12, 2009 at 23:42 Comment(1)
+1, you might want to add your statement that functions are always overridable though. In c++0x the keyword "final" should prevent overriding.Theall
A
1

As you can see below, the function A::a is successfully overridden with B::a without requiring virtual

It may, or it may not work. In your example it works, but it's because you create and use an B object directly, and not through pointer to A. See C++ FAQ Lite, 20.3.

So virtual also tells the compiler to call up the parent's destructors?

A virtual destructor is needed if you delete a pointer of base class pointing to an object of derived class, and expect both base and derived destructors to run. See C++ FAQ Lite, 20.7.

Adara answered 1/12, 2009 at 21:28 Comment(0)
C
1

You need the virtual if you use a base class pointer as consultutah (and others while I'm typing ;) ) says it.

The lack of virtuals allows to save a check to know wich method it need to call (the one of the base class or of some derived). However, at this point don't worry about performances, just on correct behaviour.

The virtual destructor is particulary important because derived classes might declare other variables on the heap (i.e. using the keyword 'new') and you need to be able to delete it.

However, you might notice, that in C++, you tend to use less deriving than in java for example (you often use templates for a similar use), and maybe you don't even need to bother about that. Also, if you never declare your objects on the heap ("A a;" instead of "A * a = new A();") then you don't need to worry about it either. Of course, this will heavily depend on what/how you develop and if you plan that someone else will derive your class or not.

Characterize answered 1/12, 2009 at 21:34 Comment(1)
It's not about where the objects are allocated, it's about using them through pointers and references to base classes, including the implicit 'this' in methods.Regurgitate
D
0

Try ((A*)&b).a() and see what gets called then.

The virtual keyword lets you treat an object in an abstract way (I.E. through a base class pointer) and yet still call descendant code...

Put another way, the virtual keyword "lets old code call new code". You may have written code to operate on A's, but through virtual functions, that code can call B's newer a().

Dimeter answered 1/12, 2009 at 21:27 Comment(0)
G
0

Say you instantiated B but held it as an instance of an A:

A *a = new B();

and called function a() whose implementation of a() will be called?

If a() isn't virtual A's will be called. If a() was virtual the instantiated sub class version of a() would be called regardless of how you're holding it.

If B's constructor allocated tons of memory for arrays or opened files, calling

delete a;

would ensure B's destructor was called regardless as to how it was being held, be it by a base class or interface or whatever.

Good question by the way.

Glottalized answered 1/12, 2009 at 21:29 Comment(1)
Oops. Thank you. Guess who's been using nothing but VB.net and C# for three years?Glottalized
F
0

I always think about it like chess pieces (my first experiment with OO).

A chessboard holds pointers to all the pieces. Empty squares are NULL pointers. But all it knows is that each pointer points a a chess piece. The board does not need to know more information. But when a piece is moved the board does not know it is a valid move as each pice has different characteristica about how it moves. So the board needs to check with the piece if the move is valid.

Piece*    board[8][8];

CheckMove(Point const& from,Point const& too)
{
    Piece*  piece = board[from.x][from.y];
    if (piece != NULL)
    {
        if (!piece->checkValidMove(from,too))
        {    throw std::exception("Bad Move");
        }
        // Other checks.
    }
}

class Piece
{
    virtual bool checkValidMove(Point const& from,Point const& too)  = 0;
};

class Queen: public Piece
{
    virtual bool checkValidMove(Point const& from,Point const& too) 
    {
         if (CheckHorizontalMove(from,too) || CheckVerticalMoce(from,too) || CheckDiagonalMove(from,too))
         {
             .....
         }
    }
}
Flux answered 2/12, 2009 at 0:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.