virtual insertion operator overloading for base and derived class

Can someone please explain how to ensure that the derived function is called from a pointer of type base to a derived object instead of the base function...

Also, are the virtual and override keywords best practice to accomplish this?

I had previously defined each overload with keyword friend in each class; but the base function was called for the base pointer to derived object.

int main()
{  
    // contrived example ...
    base* ptr_derived = new derived();
    std::cout << *ptr_derived;

    delete ptr_derived;
}
class base 
{
    virtual std::ostream& operator<<(std::ostream output)
    {
        output << // base details... 
        return output;
    }
};

class derived : public base
{
    std::ostream& operator<<(std::ostream output) // override?
    {
        output << // derived details...
        return output;
    }
};

2 answers

  • answered 2018-06-22 02:54 songyuanyao

    operator<< has to be non-member function, you can't make them virtual. You have to make another virtual member function, and call it in operator<< to make runtime dispatch work. e.g.

    class base 
    {
        friend std::ostream& operator<<(std::ostream& output, const base& b)
        {
            b.print(output);
            return output;
        }
        virtual void print(std::ostream& output) const {
            output << // base details... 
        }
    public:
        virtual ~base() = default;
    };
    
    class derived : public base
    {
        virtual void print(std::ostream& output) const override {
            output << // derived details...
        }
    };
    

  • answered 2018-06-22 02:57 Jerry Coffin

    Virtual functions work by calling the correct function through a pointer in something called the vtable. So, some offset from this gives a pointer to the vtable, and some offset into the vtable gives the address of the function for that actual type of object.

    However, this won't work (directly) with an overload of operator<< for inserting an object into a stream. A virtual function must be a member function--but when you're overloading a member function, its left operand must be an object of the type for which you're providing an overload. That is, with an overloaded member function, a << b is invoked as a.operator<<(b);. For stream insertion that won't work, because the left operand is always the stream rather than the type you're going to insert into the stream.

    To get around that, you do make the operator itself a friend (which is never a member).

    To get virtual behavior, you have that invoke a virtual member function:

    class base { 
    public:
        virtual std::ostream &write(std::ostream &os) const { 
            // write myself to the passed stream
            return os;
        }
    
        friend std::ostream &operator<<(std::ostream &os, base const &b) { 
           return b.write(os);
        }
    };
    
    class derived : public base {
    public:
        std::ostream &write(std::ostream &os) const override { 
            // write myself to the passed stream
            return os;
        }
    };
    

    Now the overloaded operator gets called for the correct types. It, in turn, just invokes the correct virtual function for the object that was actually passed (base, derived, or some other derived class if you choose to create one).