Я борюсь с расширением следующего кода:
#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;
}
Этот код работает нормально и печатает «1», а затем «2», как и ожидалось.
Но теперь я хотел бы реализовать интерфейс так:
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: Как я могу заставить это работать?
Я попытался использовать dynamic_cast и виртуальные деструкторы, чтобы сделать классы полиморфными. Я также пытался реализовать явный конструктор downcast из Base в Derived, но с треском провалился.
PS: Изменение «База» не вариант.
b
это не Derived
объект, поэтому приведение его к Derived
является неопределенное поведение.
В первом примере правильное решение — переместить методы сериализации в Base
и сделать их виртуальными / абстрактными так Derived
может переопределить их. Затем создайте Derived
возразите и снимите приведения с ваших операторов:
#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;
}
Сделайте что-то подобное во втором примере:
#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;
}
При определении b
как тип Base
и вызывающий оператор <<
где операнд приведен к Derived&
, вы получаете неопределенное поведение, потому что b
не тип Derived
, Неопределенное поведение означает, что все может произойти, включая программу, работающую так, как задумано. Небольшое изменение настроек может привести к другому «неопределенному поведению», и это то, что вы можете наблюдать.