Search code examples
c++inheritanceserializationmfccarchive

Implementing MFC Serialazation for base and derived classes


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?


Solution

  • 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];
    }