Search code examples
c++castingvirtualdowncast

Why does a virtual function break my casting?


I'm struggling with extending the following code:

#include <iostream>

class XmlTree {};

class Base
{
protected:
    int var;
public:
    Base(int var) : var(var) {}
    virtual ~Base() {}
};

class Derived : public Base
{
public:
    void SerializeTo(XmlTree& tree) const { std::cout << var << std::endl; }
    void DeserializeFrom(const XmlTree& tree) { var = 2; }
};

void operator<<(XmlTree& tree, const Base& b) { static_cast<const Derived&>(b).SerializeTo(tree); }
void operator>>(const XmlTree& tree, Base& b) { static_cast<Derived&>(b).DeserializeFrom(tree); }

int main() {
    Base b(1);
    XmlTree tree;
    tree << b;
    tree >> b;
    tree << b;
}

This code works fine and prints '1' then '2' as expected.

But now I would like to implement an interface like so:

class XmlInterface
{
public:
    virtual void SerializeTo(XmlTree& tree) const = 0;
    virtual void DeserializeFrom(const XmlTree& tree) = 0;
};

class Derived : public Base, public XmlInterface
{
public:
    virtual void SerializeTo(XmlTree& tree) const override { std::cout << var << std::endl; }
    virtual void DeserializeFrom(const XmlTree& tree) override { var = 2; }
};

TL;DR: How can I make it work?

I have tried to use dynamic_cast and virtual destructors to make the classes polymorphic. I also tried to implement an explict downcast constructor from Base to Derived, but I failed miserably.

PS: Altering 'Base' is not an option.


Solution

  • b is not a Derived object, so casting it to Derived is undefined behavior.

    In the first example, the correct solution is to move the serialize methods into Base and make them virtual/abstract so Derived can override them. Then create a Derived object and remove the casts from your operators:

    #include <iostream>
    
    class XmlTree {};
    
    class Base
    {
    protected:
        int var;
    public:
        Base(int var) : var(var) {}
        virtual ~Base() {}
        virtual void SerializeTo(XmlTree& tree) const = 0;
        virtual void DeserializeFrom(const XmlTree& tree) = 0;
    };
    
    class Derived : public Base
    {
    public:
        Derived(int var) : Base(var) {}
        void SerializeTo(XmlTree& tree) const override { std::cout << var << std::endl; }
        void DeserializeFrom(const XmlTree& tree) override { var = 2; }
    };
    
    void operator<<(XmlTree& tree, const Base& b) { b.SerializeTo(tree); }
    void operator>>(const XmlTree& tree, Base& b) { b.DeserializeFrom(tree); }
    
    int main() {
        Derived d(1);
        XmlTree tree;
        tree << d;
        tree >> d;
        tree << d;
    }
    

    Do something similar in the second example:

    #include <iostream>
    
    class XmlTree {};
    
    class Base
    {
    protected:
        int var;
    public:
        Base(int var) : var(var) {}
        virtual ~Base() {}
    };
    
    class XmlInterface
    {
    public:
        virtual void SerializeTo(XmlTree& tree) const = 0;
        virtual void DeserializeFrom(const XmlTree& tree) = 0;
    };
    
    class Derived : public Base, public XmlInterface
    {
    public:
        Derived(int var) : Base(var) {}
        void SerializeTo(XmlTree& tree) const override { std::cout << var << std::endl; }
        void DeserializeFrom(const XmlTree& tree) override { var = 2; }
    };
    
    void operator<<(XmlTree& tree, const XmlInterface& intf) { intf.SerializeTo(tree); }
    void operator>>(const XmlTree& tree, XmlInterface& intf) { intf.DeserializeFrom(tree); }
    
    int main() {
        Derived d(1);
        XmlTree tree;
        tree << d;
        tree >> d;
        tree << d;
    }