0

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.

Dominic Hofer
  • 5,751
  • 4
  • 18
  • 23

2 Answers2

2

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;
}
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
1

When defining b as being of type Base and calling operator <<, where the operand is casted to Derived&, you yield undefined behaviour because b is not of type Derived. Undefined behaviour means that everything can happen, including the program working as intended. Changing the setting a little bit can yield a different "undefined behaviour", and that's what you can observe.

Stephan Lechner
  • 34,891
  • 4
  • 35
  • 58