I\'m learning C++ and I\'m just getting into virtual functions.
From what I\'ve read (in the book and online), virtual functions are functions in the base class that
You need virtual methods for safe downcasting, simplicity and conciseness.
That’s what virtual methods do: they downcast safely, with apparently simple and concise code, avoiding the unsafe manual casts in the more complex and verbose code that you otherwise would have.
The following code is intentionally “incorrect”. It doesn’t declare the value
method as virtual
, and therefore produces an unintended “wrong” result, namely 0:
#include <iostream>
using namespace std;
class Expression
{
public:
auto value() const
-> double
{ return 0.0; } // This should never be invoked, really.
};
class Number
: public Expression
{
private:
double number_;
public:
auto value() const
-> double
{ return number_; } // This is OK.
Number( double const number )
: Expression()
, number_( number )
{}
};
class Sum
: public Expression
{
private:
Expression const* a_;
Expression const* b_;
public:
auto value() const
-> double
{ return a_->value() + b_->value(); } // Uhm, bad! Very bad!
Sum( Expression const* const a, Expression const* const b )
: Expression()
, a_( a )
, b_( b )
{}
};
auto main() -> int
{
Number const a( 3.14 );
Number const b( 2.72 );
Number const c( 1.0 );
Sum const sum_ab( &a, &b );
Sum const sum( &sum_ab, &c );
cout << sum.value() << endl;
}
In the line commented as “bad” the Expression::value
method is called, because the statically known type (the type known at compile time) is Expression
, and the value
method is not virtual.
Declaring value
as virtual
in the statically known type Expression
ensures that the each call will check what actual type of object this is, and call the relevant implementation of value
for that dynamic type:
#include <iostream>
using namespace std;
class Expression
{
public:
virtual
auto value() const -> double
= 0;
};
class Number
: public Expression
{
private:
double number_;
public:
auto value() const -> double
override
{ return number_; }
Number( double const number )
: Expression()
, number_( number )
{}
};
class Sum
: public Expression
{
private:
Expression const* a_;
Expression const* b_;
public:
auto value() const -> double
override
{ return a_->value() + b_->value(); } // Dynamic binding, OK!
Sum( Expression const* const a, Expression const* const b )
: Expression()
, a_( a )
, b_( b )
{}
};
auto main() -> int
{
Number const a( 3.14 );
Number const b( 2.72 );
Number const c( 1.0 );
Sum const sum_ab( &a, &b );
Sum const sum( &sum_ab, &c );
cout << sum.value() << endl;
}
Here the output is 6.86
as it should be, since the virtual method is called virtually. This is also called dynamic binding of the calls. A little check is performed, finding the actual dynamic type of object, and the relevant method implementation for that dynamic type, is called.
The relevant implementation is the one in the most specific (most derived) class.
Note that method implementations in derived classes here are not marked virtual
, but are instead marked override
. They could be marked virtual
but they’re automatically virtual. The override
keyword ensures that if there is not such a virtual method in some base class, then you’ll get an error (which is desirable).
Without virtual
one would have to implement some Do It Yourself version of the dynamic binding. It’s this that generally involves unsafe manual downcasting, complexity and verbosity.
For the case of a single function, as here, it suffices to store a function pointer in the object and call via that function pointer, but even so it involves some unsafe downcasts, complexity and verbosity, to wit:
#include <iostream>
using namespace std;
class Expression
{
protected:
typedef auto Value_func( Expression const* ) -> double;
Value_func* value_func_;
public:
auto value() const
-> double
{ return value_func_( this ); }
Expression(): value_func_( nullptr ) {} // Like a pure virtual.
};
class Number
: public Expression
{
private:
double number_;
static
auto specific_value_func( Expression const* expr )
-> double
{ return static_cast<Number const*>( expr )->number_; }
public:
Number( double const number )
: Expression()
, number_( number )
{ value_func_ = &Number::specific_value_func; }
};
class Sum
: public Expression
{
private:
Expression const* a_;
Expression const* b_;
static
auto specific_value_func( Expression const* expr )
-> double
{
auto const p_self = static_cast<Sum const*>( expr );
return p_self->a_->value() + p_self->b_->value();
}
public:
Sum( Expression const* const a, Expression const* const b )
: Expression()
, a_( a )
, b_( b )
{ value_func_ = &Sum::specific_value_func; }
};
auto main() -> int
{
Number const a( 3.14 );
Number const b( 2.72 );
Number const c( 1.0 );
Sum const sum_ab( &a, &b );
Sum const sum( &sum_ab, &c );
cout << sum.value() << endl;
}
One positive way of looking at this is, if you encounter unsafe downcasting, complexity and verbosity as above, then often a virtual method or methods can really help.
When you have a function in the base class, you can Redefine
or Override
it in the derived class.
Redefining a method :
A new implementation for the method of base class is given in the derived class. Does not facilitate Dynamic binding
.
Overriding a method:
Redefining
a virtual method
of the base class in the derived class. Virtual method facilitates Dynamic Binding.
So when you said :
But earlier in the book, when learning about basic inheritance, I was able to override base methods in derived classes without using 'virtual'.
you were not overriding it as the method in the base class was not virtual, rather you were redefining it