1

I have two classes: Product and derived Juice. I need to implement MFC Serialazation for these classes.

class Product : CObject
{
protected:
    DECLARE_SERIAL(Product) //IMPLEMENT_SERIAL(Product, CObject, 0) in .cpp

    CString name;
    int expiring;
    double price;
public:
    Product();
    ~Product();

    virtual void input_data();
    virtual void print_data();

    virtual void Serialize(CArchive& archive)
    {
        CObject::Serialize(archive);

        if (archive.IsStoring())
            archive << name << expiring << price;
        else
            archive >> name >> expiring >> price;
    };

};
class Juice :
    public Product
{

private:
    double volume; 
    CString taste;
public:
    Juice();
    ~Juice();

    void input_data() override;
    void print_data() override;

    void Serialize(CArchive& archive) override
    {
        Product::Serialize(archive);
        if (archive.IsStoring())
            archive << volume << taste;
        else
            archive >> volume >> taste;
    }

};

To store objects of the classes I have Stock class which has container of Product class pointers.

class Stock
{
private:
    vector<shared_ptr<Product>> stock;
public:
    Stock();
    ~Stock();

    void Add(shared_ptr<Product> p); 
    void Print(); 

    bool Save(string fname);
    bool Load(string fname);

    void Clear();
};

In Save and Load methods I'm trying to implement serialazation (according to discussion in this topic C++ MFC Serialization).

bool Stock::Save(string fname)
{
    CFile out;
    if (!out.Open(fname.c_str(), CFile::modeWrite | CFile::modeCreate))
        return false;

    CArchive ar(&out, CArchive::store);
    ar.WriteCount(stock.size());
    for (auto it = stock.begin(); it != stock.end(); ++it)
    {
        (*it)->Serialize(ar);
    }
    ar.Close();
    out.Close();
    return true;
}

bool Stock::Load(string fname)
{
    CFile in;
    if (!in.Open(fname.c_str(), CFile::modeRead))
        return false;

    CArchive ar(&in, CArchive::load);
    int cnt = ar.ReadCount();
    for (int i = 0; i < cnt; i++)
    {
        auto p = make_shared<Product>();
        p->Serialize(ar);
        stock.push_back(p);
    }
    ar.Close();
    in.Close();
    return true;
}

Now I got a problem.

While reading objects from file Juice objects are read like Product (without volume ant taste fields). The reading of the object after Juice starts with the rest of Juice information, so I got CArchiveException in Serialaize method of Product.

exception

If I use only Product objects to add to Stock everything works fine. What are my mistakes and what should I do to implement MFC serialization correctly?

st_dec
  • 142
  • 1
  • 11
  • 1
    You need a DECLARE_SERIAL in your Juice class. – Mercury Dime Oct 01 '19 at 12:28
  • Yeah. I made public inheritance for Product and added DECLARE_SERIAL(Juice) and IMPLEMENT_SERIAL(Juice, CObject, 0) for Juice class. But still have the same problems – st_dec Oct 01 '19 at 12:40
  • Base class for Juice = Product, not CObject – Mercury Dime Oct 01 '19 at 12:42
  • Should I use IMPLEMENT_SERIAL(Juice, Product, 0)? I did it but still got same problems. If I don't add Juice objects to Stock object, everything works fine – st_dec Oct 01 '19 at 12:58
  • Just for investigation's sake, how hard would it be for you to 'temporarily' change the `stock` member of the `Stock` class to a vector of ***raw pointers***? Just thinking: Maybe it's a bug/conflict between the STL and MFC's weird implementation of `DECLARE_SERIAL` polymorphism? – Adrian Mole Oct 01 '19 at 14:01
  • Seems to me that, at some point during adding to the `Stock` vector, your `Juice` objects are being improperly downcast (or sliced) into `Product` objects. Trouble is, there are so many layers here where (presumably MSVC) implementation of the STL and/or MFC could be problematical. – Adrian Mole Oct 01 '19 at 14:07
  • I'll try it now – st_dec Oct 01 '19 at 14:12
  • It's the serialize call for each element that's the problem. You need to use ar << element, not element.Serialize(ar); I'll post something below. – Mercury Dime Oct 01 '19 at 14:15
  • I saw this note in tutorial but not sure how to do it properly. I can't use << with my pointers and I tried to cast them to CObject* but still can't use < – st_dec Oct 01 '19 at 14:18
  • @Adrian Changing stock member to a vector of raw pointers doesn't help. Using << and >> operators suggested by Mercury Dime is right way to change my program – st_dec Oct 01 '19 at 14:37
  • It was just a suggestion! I'm glad the answer from @MercuryDime worked … however, I'll give myself some 'cred' for mentioning MFC's "weird `DECLARE_SERIAL` polymorphism!" :-) – Adrian Mole Oct 01 '19 at 14:39

1 Answers1

2

Stock::Save needs to change to:

for (auto it = stock.begin(); it != stock.end(); ++it)
{
    ar << (*it).get();
}

And Stock::Load needs to change to:

for (int i = 0; i < cnt; i++)
{
    Product* obj = nullptr;
    ar >> obj;

    stock.emplace_back(obj);
}

When you use ar << obj, it saves type information with the object so it can be retrieved properly when you load it. Calling Serialize directly won't save the type data.

For reference, here's what the MFC serialization code looks like inside of CObArray (basically what you'd use instead of a vector if you stuck to MFC only)

if (ar.IsStoring())
{
    ar.WriteCount(m_nSize);
    for (INT_PTR i = 0; i < m_nSize; i++)
        ar << m_pData[i];
}
else
{
    DWORD_PTR nOldSize = ar.ReadCount();
    SetSize(nOldSize);
    for (INT_PTR i = 0; i < m_nSize; i++)
        ar >> m_pData[i];
}
Mercury Dime
  • 1,141
  • 2
  • 10
  • 10