Implementing MFC Serialazation for base and derived classes - c++

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.
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?

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

Related

C++ OOP Abstract Class - Access violation writing location

I have an UserAcount class that has an abstract class ContBancar, and other class Banca which reads some users from a file (with method void Banca::citire_conturi()). When it reads the users, I get an error "Access violation writing location" in ContBancar at void setBal(double bal) { _balanta = bal; }. Thx for help !
PS : The file has only one line : 1CBS Dragos 0 dragos12! Gzpvia01= .
Also, i want to make a bank account system, with an user class that has an bank account class which inherits 3 types of a bank accounts, and a bank class which reads some users from a file or put them on it.
class UserAccount
{
private:
std::string _nume, _user, _pass;
std::string _cod_us;
std::shared_ptr <ContBancar> _cont;
public:
void setUser(std::string user) { _user = user; }
void setPass(std::string pass) { _pass = pass; }
void setNume(std::string nume) { _nume = nume; }
void setCodUs(std::string cod) { _cod_us = cod; }
void setContBal(double balanta) { (*_cont).setBal(balanta); }
std::string getUser() const { return _user; }
std::string getPass() const { return _pass; }
std::string getNume() const { return _nume; }
std::string getCodUs() const { return _cod_us; }
double getContBal() const { return (*_cont).getBal(); }
void setContBancar();
};
void UserAccount::setContBancar()
{
if (_cod_us == "1CBS")
_cont.reset(new ContBancarSilver());
else if (_cod_us == "2CBG")
_cont.reset(new ContBancarGold());
else
_cont.reset(new ContBancarDiamond());
}
class ContBancar
{
protected:
double _balanta;
public:
void setBal(double bal) { _balanta = bal; }
double getBal() { return _balanta; }
virtual bool depozitare(unsigned int) = 0;
virtual bool retragere(unsigned int) = 0;
};
class Banca
{
private:
std::vector<UserAccount> vec;
public:
void citire_conturi();
};
void Banca::citire_conturi()
{
std::ifstream file;
file.open("Baza_Date.txt");
UserAccount temp;
std::string cod, nume, user, pass;
double balanta;
while (file >> cod >> nume >> balanta >> user >> pass)
{
temp.setCodUs(cod);
temp.setNume(nume);
temp.setContBal(balanta);
temp.setUser(user);
temp.setPass(pass);
vec.push_back(temp);
}
file.close();
}
class ContBancarSilver : public ContBancar
{
private:
static constexpr unsigned int max_balanta = 5000;
static constexpr unsigned int max_depozitare = 2500;
static constexpr unsigned int max_retragere = 1000;
static constexpr double tax_retragere = 0.08;
static constexpr double bonus_depunere = 0.03;
static constexpr double bonus_tax_retragere = 0.05;
static constexpr unsigned int max_depozitari = 1;
static constexpr unsigned int max_retrageri = 1;
public:
virtual bool depozitare(unsigned int) override;
virtual bool retragere(unsigned int) override;
};
Based on available informationyou should fix your code like this:
class UserAccount
{
.....
void setCodUs(std::string cod) {
_cod_us = cod;
setContBancar();
}
void setContBal(double balanta) {
if (!_cont) setContBancar(); // lazy initialization
_cont->setBal(balanta);
}
...
};
void UserAccount::setContBancar()
{
if (_cod_us == "1CBS")
_cont = std::make_shared<ContBancarSilver>();
else if (_cod_us == "2CBG")
_cont = std::make_shared<ContBancarGold>();
else
_cont = std::make_shared<ContBancarDiamond>();
}
Note I do not understand what kind of logic you are implementing. This changes just ensured that _cont is initialized and up to date with _cod_us.
Please stop use explicitly new and delete. Everything can be created by std::make_shared and std::make_unique and containers like std::vector.

How to fix error C2259: cannot instantiate abstract class

I'm doing an assignment with virtual classes, I need to implement a pure virtual method which later will be implemented in an inherited class.
I solved a problem before with pure virtual methods which worked flawlessly, solving by myself the error i receive now ( error C2259: 'Playlist': cannot instantiate abstract class) - by implementing a method in an inherited class.
class Record
{
char* artist;
char* titlu;
int durata;
public:
Record()
{
artist = new char[50];
titlu = new char[50];
durata = 0;
}
void setArtist(char *s)
{
strcpy(artist, s);
}
char* getArtist()
{
return artist;
}
void setTitlu(char* s)
{
strcpy(titlu, s);
}
char* getTitlu()
{
return titlu;
}
void setDurata(int n)
{
int durata = n;
}
int getDurata()
{
return durata;
}
};
class Playlist
{
Record* p; /// "Record" is another class
public:
Playlist(int n)
{
p = new Record[n];
}
virtual void sortare(int n) = 0;
};
class PlaylistImplementation :public Playlist
{
public:
void sortare(int n)
{
if (n == 1)
{
cout << "1";
}
else if (n == 2)
{
cout << "2";
}
else
cout << "3";
}
};
Here is the main():
int main()
{
int n;
cout << "Number of tracks ?";
cin >> n;
Playlist x(n); /// I use VS 2019 and "x" has a red tilde underneath,
/// also error is at this line.
cin.ignore();
cin.get();
return 0;
}
I expect to instantiate an object from class Playlist.
You can't instantiate Playlist directly because it is abstract, so in main, instead of:
Playlist x(n);
you need
PlaylistImplementation x(n);
And you need a constructor in PlaylistImplementation like so:
PlaylistImplementation (int n) : PlayList (n) { }
Member 'p' of type Record in class Playlist is implicitly declared private. For access you need to add public member functions (e.g. to store data to a record).

Should be a virtual destructor? But how?

A program that stores a phone company's consumers data in a linked list. At the end it displays the bill for each human. I have the following codes:
class BaseTypeOfContract
{
private:
int minutePrice;
int SMSPrice;
public:
void setminutePrice(int x) { minutePrice = x; }
void setSMSPrice(int x) { SMSPrice = x; }
virtual int calculateBill(int talkedMinutes, int sentSMS) = 0;
int getminutePrice() const { return minutePrice; }
int getSMSPrice() const { return SMSPrice; }
};
class SMSBaseType : public BaseTypeOfContract
{
private:
int freeSMS;
public:
SMSBaseType(int minutePrice, int SMSPrice, int freeSMS)
{
setminutePrice(minutePrice);
setSMSPrice(SMSPrice);
setfreeSMS(freeSMS);
}
public:
void setfreeSMS(int free) { this->freeSMS = free; }
virtual int calculateBill(int talkedMinutes, int sentSMS)
{
int billedSMS = (freeSMS > sentSMS) ? 0 : sentSMS - freeSMS;
return talkedMinutes * getminutePrice() + billedSMS * getSMSPrice();
}
};
class Base : public BaseTypeOfContract
{
public:
Base()
{
setminutePrice(30);
setSMSPrice(10);
}
virtual int calculateBill(int talkedMinutes, int sentSMS) { return talkedMinutes * getminutePrice() + sentSMS * getSMSPrice();}
};
class SMSMax : public SMSBaseType
{
public:
SMSMax() : SMSBaseType(20, 5, 150) {}
};
class MobiNET: public SMSBaseType
{
public:
MobiNET() : SMSBaseType(10, 15, 25) {}
};
Client's class:
class Client
{
public:
std::string name;
std::string phoneNumber;
BaseTypeOfContract* typeOfContract;
int talkedMinutes;
int sentSMS;
Client *next;
public:
Client(){}
Client(std::string n, std::string p, int bp, int ks) : name(n), phoneNumber(p), talkedMinutes(bp), sentSMS(ks) {}
void preSetPlan(std::string s)
{
if (s == "MobiNET")
this->typeOfContract = new MobiNET();
else if (s == "SMSMax")
this->typeOfContract = new SMSMax();
else this->typeOfContract = new Base();
}
std::string getname() const { return name; }
std::string getphoneNumber() const { return phoneNumber; }
void setname(std::string n) { name = n; }
void setphoneNumber(std::string pn) { phoneNumber = pn; }
void settalkedMinutes(int bp) { talkedMinutes = bp; }
void setsentSMS(int SSMS) { sentSMS = SSMS; }
int getBill() const { return this->typeOfContract->calculateBill(talkedMinutes, sentSMS); }
};
I read the data from 2 files. First file contains the name, phone number, type of contract. Second file contains the phone number, talked minutes and sent SMS.
Client* file_read_in()
{
std::ifstream ClientData;
ClientData.open("clients.txt");
Client *first = new Client;
first = NULL;
while (!ClientData.eof())
{
std::string name, phoneNumber, typeOfContract;
ClientData >> name;
ClientData >> phoneNumber;
ClientData >> typeOfContract;
std::ifstream ClientTalkedSent;
ClientTalkedSent.open("used.txt");
while(!ClientTalkedSent.eof())
{
std::string phoneNumber2;
ClientTalkedSent >> phoneNumber2;
if (phoneNumber2 == phoneNumber)
{
int talkedMinutes, sentSMS;
ClientTalkedSent >> talkedMinutes;
ClientTalkedSent >> sentSMS;
Client* tmp = new Client(name, phoneNumber, talkedMinutes, sentSMS);
tmp->preSetPlan(typeOfContract);
tmp->next = NULL;
if (first == NULL)
{
first = tmp;
}
else
{
Client *cond = first;
while (cond->next != NULL) cond = cond->next;
cond->next = tmp;
}
}
}
ClientTalkedSent.close();
}
ClientData.close();
return first;
}
And the main:
int main()
{
Client* first = file_read_in();
while(first != NULL)
{
std::cout << first->getname() << " " << first->getphoneNumber() << " " << first->getBill() << std::endl;
first = first->next;
}
return 0;
}
My problem that I should free the allocated memory but I got on idea how. Which class' destructor should do the dirty job. I would appreciate if someone could use my code, to show how the "destructor inheritance" works.
Sorry for my bad english and thanks for the help. This site helped me alot of times, but for this problem I did not find a solution.
If you have a pointer BaseTypeOfContract* typeOfContract; that is used to point to different derived classes, then BaseTypeOfContract needs to have a virtual destructor for delete typeOfContract to work.
And as Client seems to create the objects pointed to, it also ought to be responsible for cleaning them up. Either by using delete typeOfContract; in its destructor, or by storing a smart pointer to get the work done automatically.
The other part is that each Client stores a pointer to the next Client. That seems like not the best design. In real life it is not at all like each person knowing who is the next person that buys a cell phone in the same store. :-)
You would be much better of with a container, like std::vector<Client>, that would also handle the lifetime of the Client objects.

C++ MFC Serializre(CArchive &ar) with CArray<class*, class*>

I have a program,draws shapes: Lozenge, square, rectangle, line, circle... it's same Paint in Microsoft.
My problems are save and load file with Serialize(CArchive &), but i cannot save and load file when use CArray. How i can do that:
class BaseShape : public CObject
{
DECLARE_SERIAL(BaseShape)
public:
CPoint topLeft, bottomRight;
COLORREF m_clrBack;
EShapeType m_ShapeType; //enum type of shape
public:
BaseShape(void); //empty method
BaseShape (CPoint , CPoint, COLORREF, EShapeType);
~BaseShape(void);
virtual void DrawShape (CDC*); //empty method
void Serialize(CArchive& ar);
};
Implement Serialize(CArchive& ar) of BaseShape class:
IMPLEMENT_SERIAL(BaseShape, CObject, 1)
void BaseShape::Serialize(CArchive& ar)
{
if (ar.IsStoring())
{
ar << topLeft << bottomRight << m_clrBack << m_ShapeType;
}
else
{
int temp_shape;
ar >> topLeft >> bottomRight >> m_clrBack >> temp_shape;
m_ShapeType = (EShapeType)temp_shape;
}
}
Square class and Lozenge class are derived by BaseShape:
class CSquare : public BaseShape
{
public:
CSquare(void);
CSquare (CPoint , CPoint, COLORREF, EShapeType);
~CSquare(void);
void DrawShape(CDC*);
};
In MFC Document class, i have:
//declare properties
CArray<BaseShape*, BaseShape*> m_arrShape;
COLORREF m_clrBack;
EShapeType m_ShapeType;
//implement method
void CdemoDoc::Serialize(CArchive& ar)
{
if (ar.IsStoring())
{
int i;
ar << m_arrShape.GetSize();
for (i = 0; i <m_arrShape.GetSize(); i++)
ar << m_arrShape[i];
}
else
{
int ncount, i;
ar >> ncount;
m_arrShape.RemoveAll();
for (i = 0; i < ncount; i++)
{
BaseShape* pShape = new BaseShape();
ar >> pShape;
m_arrShape.Add(pShape);
}
above my code, When i click open file, the shapes is not showed, which was drew before, although my code are not error, I save data file uncertain successfuly. I dont understand how the code lines of "isloading()" function working. Is there another way to do it ? This is my source code for all my project: http://www.mediafire.com/download/jy23ct28bgqybdc/demo.rar
The reason is simple: You can not create a BaseShape object and expect that it get specialized when it is loaded.
The trick is that ar << saves the complete object type and saves the Name of the class in the stream to. All you need is to load the stream into an object pointer again. The CArchive code will try to find a matching object class creates it and loads it.
Please read the MSDN about CObject serializing... se also the Scribble samples and others.
if (ar.IsStoring())
{
int i;
ar << m_arrShape.GetSize();
for (i = 0; i <m_arrShape.GetSize(); i++)
ar << m_arrShape[i];
}
else
{
int ncount, i;
ar >> ncount;
m_arrShape.RemoveAll();
for (i = 0; i < ncount; i++)
{
BaseShape *pNewShape;
ar >> pNewShape;
m_arrShape.Add(pNewShape);
}
}
PS: If you provide sample code, it should match the code in your question.

MFC serialization

i have to serialize a CList of base and derived classes. How can i do that.
typedef CList<Student*, Student*> StVec;
class Student : public CObject
{
public:
DECLARE_SERIAL(Student);
Student();
Student(Student& s);
Student(CString _name, CString _last, int _age);
virtual ~Student(void);
virtual void cin();
virtual void cout();
virtual void Serialize(CArchive& ar);
static Student* FromInput();
inline const CString GetName(){return name;}
inline const CString GetLastName(){return last_name;}
inline const int GetAge(){return age;}
virtual inline Student& operator=( const Student &s )
{
name = s.name;
last_name = s.last_name;
age = s.age;
return *this;
}
protected:
CString name;
CString last_name;
int age;
};
class Warden : public Student
{
protected:
virtual void Serialize(CArchive& ar);
UINT salary;
};
class Group : public CObject
{
public:
DECLARE_SERIAL(Group);
enum FindType { First = 0, Last, All};
typedef bool (*StudentsFindCallback) (Student* student, void* condition);
Group(){ name = _T("Default Group"); }
Group(CString _name);
Group(Group& s);
~Group();
void CreateUser();
void ShowUsers();
//StVecOfIt FindUser(StudentsFindCallback f, void* condition, FindType ft);
inline Student* getStudent(POSITION pos) {return students->GetNext(pos); }
inline void DeleteUser(POSITION it){ students->RemoveAt(it); }
virtual void Serialize(CArchive& ar);
void Clean();
/*static bool FindbyName(Student* student, void* condition);
static bool FindbyLastName(Student* student, void* condition);
static bool FindbyAge(Student* student, void* condition);*/
virtual inline Group& operator=( const Group &s )
{
name = s.name;
students = s.students;
return *this;
}
protected:
CString name;
StVec* students;
};
void Student::Serialize(CArchive& ar)
{
CObject::Serialize( ar );
if (ar.IsStoring())
{
ar.SerializeClass(Student::GetRuntimeClass());
ar << name << last_name << age;
}
else
{
ar.SerializeClass(Student::GetRuntimeClass());
ar >> name >> last_name >> age;
}
}
void Group::Serialize(CArchive& ar)
{
ar.SerializeClass(RUNTIME_CLASS(Group));
if (ar.IsStoring())
{
ar << (int)students->GetCount();
POSITION pos = students->GetHeadPosition();
while(pos != NULL)
{
Student* student = students->GetNext(pos);
student->Serialize(ar);
}
}
else
{
Clean();
int count = 0;
ar >> count;
for(INT_PTR i = 0; i < count; ++i)
{
Student* st = new Student();
st->Serialize(ar);
students->AddTail(st);
}
}
}
I found an article at the codeproject but i'cant understend it at all.
Ofcourse i can add a flag which indicate the type of class. Also i can serialize to derived class and if the derived properties are empty use static_cast to derive it to base class. Or i'am thinking of using CArchive::ReadObject or CArchive::ReadClass to load classes, but is there another way to do it?
My MFC is rusty but I believe this should work (not tested!).
Also, your Serialize() method should never call SerializeClass for its own class. It's up to the caller to decide to call it. For instance, the << and >> operators do it to identify the class of the object that comes next in the archive.
void Student::Serialize( CArchive& ar )
{
CObject::Serialize( ar );
if (ar.IsStoring())
{ // No need for SerializeClass here.
ar << name << last_name << age;
}
else
{ // No need for SerializeClass here.
ar >> name >> last_name >> age;
}
}
void Group::Serialize(CArchive& ar)
{
if (ar.IsStoring())
{
ar << (int)students->GetCount();
POSITION pos = students->GetHeadPosition();
while(pos != NULL)
{
Student* student = students->GetNext(pos);
ar << student; // << includes class info about the serialized object
}
}
else
{
Clean();
int count = 0;
ar >> count;
for(INT_PTR i = 0; i < count; ++i)
{
CObject *p;
ar >> p; // Deserialize whatever kind of object comes next
students->AddTail(p);
}
}
}