Why do we need virtual functions in C++?

前端 未结 26 3161
北恋
北恋 2020-11-21 05:50

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

相关标签:
26条回答
  • 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.


    Non-virtual method ⇒ static binding

    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.


    Virtual method ⇒ dynamic binding.

    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).


    The ugliness of doing this without virtual methods

    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.

    0 讨论(0)
  • 2020-11-21 06:25

    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

    0 讨论(0)
提交回复
热议问题