Why do we need virtual functions in C++?

前端 未结 26 3686
北恋
北恋 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条回答
  •  不要未来只要你来
    2020-11-21 06:21

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

提交回复
热议问题