C++. Examples of saving/reading class objects. Serialization

Examples of saving/reading class objects. Serialization

This topic contains examples of serializing class objects. In the most general case, serialization is the property of an object to store its state in a file stream and (on demand) restore it.


Contents


Search other resources:

1. An example of developing a class that contains the means of writing to a file and reading from a file. The class implements an array of numbers of type double*

The example shows the property of a class instance to save its state in a file and restore it from a file. The object state is the current value of the internal data (internal fields) of the object.

A class has been developed that contains the following components:

  • A – array of numbers of double type;
  • count – current number of elements in the array;
  • constructor ArrayNumbers(double*, int) – initializes the internal array with external data;
  • ArrayNumbers() constructor – creates a zero-length array;
  • copy constructor ArrayNumbers(const ArrayNumbers&);
  • operator function operator=(const ArrayNumbers&);
  • destructor ~ArrayNumbers();
  • method Print() – displays the contents of the current instance;
  • method SaveToFile(char*) – writes the current instance to a file;
  • method ReadFromFile(char*) – reads the current instance from a file.

 

#include <iostream>
#include <fstream>
using namespace std;

// Array of numbers
class ArrayNumbers
{
private:
  // Internal fields
  double* A;
  int count;

public:
  // Constructors
  ArrayNumbers(double* _A, int _count)
  {
    try
    {
      count = _count;
      A = new double[count];
      for (int i = 0; i < _count; i++)
        A[i] = _A[i];
    }
    catch (bad_alloc e)
    {
      cout << e.what() << endl;
    }
  }

  // Constructor without a parameters
  ArrayNumbers()
  {
    count = 0;
  }

  // Copy constructor
  ArrayNumbers(const ArrayNumbers& obj)
  {
    try
    {
      count = obj.count;
      A = new double[count];
      for (int i = 0; i < count; i++)
        A[i] = obj.A[i];
    }
    catch (bad_alloc e)
    {
      cout << e.what() << endl;
    }
  }

  // Copy operator
  ArrayNumbers& operator=(const ArrayNumbers& obj)
  {
    // Release pre-allocated memory
    if (count > 0)
      delete[] A;

    try
    {
      count = obj.count;
      A = new double[count];
      for (int i = 0; i < count; i++)
        A[i] = obj.A[i];
    }
    catch (bad_alloc e)
    {
      cout << e.what() << endl;
    }
  }

  // Destructor
  ~ArrayNumbers()
  {
    if (count > 0)
      delete[] A;
  }

  // Function to display the contents of the current instance
  void Print(string msg)
  {
    cout << msg;
    cout << ":   A => { ";
    for (int i = 0; i < count; i++)
    {
      cout << A[i] << " ";
    }
    cout << "}" << endl;
  }

  // Serialization methods
  // Writing the current instance to a file
  bool SaveToFile(const char* filename)
  {
    // 1. Create an instance of the ofstream class
    ofstream outF(filename, ios::out | ios::binary);

    if (!outF)
      return false;

    // 2. Write number of elements in array
    outF.write((char*)&count, sizeof(count));

    // 3. Write the array
    for (int i = 0; i < count; i++)
    {
      outF.write((char*)&A[i], sizeof(A[i]));
    }

    // 4. Close the file
    outF.close();

    // 5. Return true
    return true;
  }

  // Reading an array from a file and writing to the current instance
  bool ReadFromFile(const char* filename)
  {
    // 1. Create an instance of the ifstream class, attempting to open a file
    ifstream inF(filename, ios::in | ios::binary);

    if (!inF)
      return false;

    // 2. First release pre-allocated memory for array
    if (count > 0)
      delete[] A;

    // 3. Read the number of elements in array
    inF.read((char*)&count, sizeof(int));

    try
    {
      // 4. Attempt to allocate memory for the elements of array
      A = new double[count];
    }
    catch (bad_alloc e)
    {
      // 5. If the attempt is unsuccessful, then exit the function
      cout << e.what();
      inF.close(); // close the file
      return false;
    }

    // 6. Read the array element by element
    double x;
    for (int i = 0; i < count; i++)
    {
      inF.read((char*)&x, sizeof(double)); // read 1 element of type double
      A[i] = x;
    }

    // 7. Close the file stream
    inF.close();

    // 8. Exit with code true - the file has been read
    return true;
  }
};

void main()
{
  // 1. Create a test array
  double AD[] = { 2.8, 3.5, 1.4, 7.0, 2.5 };

  // 2. Create an instance A1 of the ArrayNumbers class and write it to the file file1.bin
  ArrayNumbers A1(AD, 5);
  A1.Print("A1"); // A1 = { 2.8, 3.5, 1.4, 7.0, 2.5 }
  A1.SaveToFile("file1.bin");

  // 3. Create an instance A2 of the ArrayNumbers class that contains a null array
  //    and fill it with data from the file file1.bin
  ArrayNumbers A2;
  A2.Print("A2"); // A2 = { }
  A2.ReadFromFile("file1.bin");
  A2.Print("A2"); // A2 = { 2.8, 3.5, 1.4, 7.0, 2.5 }
}

Program result

A1: A => { 2.8 3.5 1.4 7 2.5   }
A2: A => { }
A2: A => { 2.8 3.5 1.4 7 2.5   }

 

2. An example of writing/reading a class object containing fields of the string type

To write a string of type string to a binary file, you need to perform the following sequence of actions:

  • get the length of the string (number of characters). This is an integer of type int;
  • write the length of the line to the file;
  • write each character of the string to a file using a loop.

To read a line from a file into an object of type string, you need to follow the following sequence:

  • read the length of a line from a file as int type;
  • in the loop, read the entire line character by character into some memory area or use the addition of a character to the end of the line in each iteration of the loop.

The example implements the serialization of an object of type BOOK. The BOOK class describes a book containing the following fields:

  • author – the name of the author of the book. This field is of type string;
  • title – title of the book, field of the string type;
  • year – publication year;
  • price – price of the book.

The class also contains a number of methods for operating with data about the book and saving/reading this data:

  • BOOK() – parameterized constructor;
  • Author() – a method that returns the name of the author of the book;
  • Title() – title of the book;
  • Year() – publication year;
  • Price() – price of the book;
  • SaveToFile() – method that writes the current value of internal fields to a file;
  • ReadFromFile() – method that reads field values from a file.

The text of the demo program is as follows.

#include <iostream>
#include <fstream>
using namespace std;

// Class describing the book
class BOOK
{
private:
  // Internal fields
  string author; // author
  string title;   // book title
  int year;       // publishing year
  float price;   // cost

public:
  // Constructor
  BOOK(string _author, string _title, int _year, float _price)
  {
    author = _author;
    title = _title;
    year = _year;
    price = _price;
  }

  // Methods for reading data
  string Author() { return author; }
  string Title() { return title; }
  int Year() { return year; }
  float Price() { return price; }

  // Method that displays field values (internal state of object)
  void Print(string msg)
  {
    cout << msg << endl;
    cout << "Author = " << author << endl;
    cout << "Title = " << title << endl;
    cout << "Year = " << year << endl;
    cout << "Price = " << price << endl;
    cout << "-----------------------------" << endl;
  }

  // Methods for writing/reading current data (serialization)
  bool SaveToFile(const char* filename)
  {
    // 1. Create an instance of the ofstream class
    ofstream outF(filename, ios::out | ios::binary);

    // 2. Checking if the file has been created
    // if not created then exit with code false
    if (!outF.is_open()) return false;

    // 3. Write fields data
    // 3.1. Field author of type string
    // 3.1.1. Determine the length of a string of type string
    int len = author.length();

    // 3.1.2. Write the length of the author string
    outF.write((char*)&len, sizeof(int));

    // 3.1.3. Write each character of a string in a loop
    for (int i = 0; i < author.length(); i++)
      outF.write((char*)&author[i], sizeof(author[i]));

    // 3.2. The title field of string type
    // 3.2.1. Get string length
    len = title.length();

    // 3.2.2. Write the length of the title string
    outF.write((char*)&len, sizeof(int));

    // 3.2.3. Write a string character by character
    for (int i = 0; i < title.length(); i++)
      outF.write((char*)&title[i], sizeof(title[i]));

    // 3.3. The year field of type int
    outF.write((char*)&year, sizeof(int));

    // 3.4. The price field of float type
    outF.write((char*)&price, sizeof(float));

    // 4. Close the file
    outF.close();

    // 5. Return code
    return true;
  }

  // Reading an array from a file and writing to the current instance
  bool ReadFromFile(const char* filename)
  {
    // 1. Create an instance of the ifstream class
    ifstream inF(filename, ios::in | ios::binary);

    // 2. Checking if a file was opened successfully
    if (!inF.is_open()) return false;

    // 3. Reading the fields of an object
    // 3.1. The author field
    // 3.1.1. Read the number of elements in the field author
    int len;
    inF.read((char*)&len, sizeof(int));

    // 3.1.2. Read a string in a loop
    char c;
    author = "";
    for (int i = 0; i < len; i++)
    {
      inF.read((char*)&c, sizeof(c));
      author += c;
    }

    // 3.2. The title field of string type
    // 3.2.1. Get the length of string
    inF.read((char*)&len, sizeof(int));

    // 3.2.2. Read a string in a loop
    title = "";
    for (int i = 0; i < len; i++)
    {
      inF.read((char*)&c, sizeof(c));
      title += c;
    }

    // 3.3. The field year of int type
    inF.read((char*)&year, sizeof(int));

    // 3.4. The price field of float type
    inF.read((char*)&price, sizeof(float));

    // Close the file
    inF.close();

    // Return code
    return true;
  }
};

void main()
{
  // 1. Create the object of BOOK type
  BOOK obj1("Author", "Title", 2000, 2.55);

  // 2. Save the obj1 object
  obj1.Print("obj1");

  // 3. Save to file obj1 => "obj1.bin"
  obj1.SaveToFile("obj1.bin");

  // 4. Create another object of type BOOK
  BOOK obj2("--", "--", 3000, 10.88);
  obj2.Print("obj2");

  // 5. Read data from file obj1.bin to obj2 object
  //    "obj1.bin" => obj2
  obj2.ReadFromFile("obj1.bin");

  // 6. Print the object
  obj2.Print("obj2 <= obj1.bin");
}

Program result

obj1
Author = Author
Title = Title
Year = 2000
Price = 2.55
-----------------------------
obj2
Author = --
Title = --
Year = 3000
Price = 10.88
-----------------------------
obj2 <= obj1.bin
Author = Author
Title = Title
Year = 2000
Price = 2.55
-----------------------------

 

3. An example of writing/reading a class object containing nested objects of another class

This example considers the case when objects (instances) of another class are declared in a class, and these objects need to be correctly saved and read. According to the above example, you can save and read structure variables (structures).

To save nested objects in a class, you need to directly get the data of these objects in the form of primitive types (int, doulbe, char, etc.).

When writing and reading data (internal fields) of objects, it is important to follow the following rule:

  • the sequence of the data to be written must match the sequence of the data to be read. So, for example, if data x, y, z are written, then in the same order these data should be read.

The example demonstrates saving an instance of the Line class, which has two built-in objects of type Point. For writing/reading in the Line class, two methods SaveToFile() and ReadFromFile() are respectively implemented.

The text of the demo program is as follows

#include <iostream>
#include <fstream>
using namespace std;

// Class describing a point on a coordinate plane
class Point
{
private:
  // Internal fields
  double x, y;

public:
  // Constructor
  Point(double _x, double _y) : x(_x), y(_y) { }

  // Access methods
  void SetXY(double _x, double _y)
  {
    x = _x; y = _y;
  }
  double X() { return x; }
  double Y() { return y; }

  // Method that displays x,y values
  void Print(string msg)
  {
    cout << msg << " => x = " << x << ", y = " << y << endl;
  }
};

// A class that describes a line
class Line
{
private:
  Point p1; // first point
  Point p2; // second point

public:
  // Constructor
  Line(double _x1, double _y1, double _x2, double _y2) :
    p1(_x1, _y1), p2(_x2, _y2) { }

  // Access methods
  Point P1() { return p1; }
  Point P2() { return p2; }

  // Method that displays the coordinates of the points of the line
  void Print(string msg)
  {
    cout << msg << endl;
    p1.Print("p1");
    p2.Print("p2");
    cout << "-----------------------" << endl;
  }

  // Methods that write/read data
  bool SaveToFile(const char* filename)
  {
    // 1. Create an instance of outF that is associated with the file filename,
    //    here:
    //    -   ios::out - the file is opened for writing;
    //    -   ios::binary - the file is opened in binary mode.
    ofstream outF(filename, ios::out | ios::binary);

    // 2. Checking if a file has been created
    if (!outF.is_open()) return false;

    // 3. Write data of fields p1, p2 of type Point
    double x1 = p1.X();
    double y1 = p1.Y();
    double x2 = p2.X();
    double y2 = p2.Y();

    outF.write((char*)&x1, sizeof(x1));
    outF.write((char*)&y1, sizeof(y1));
    outF.write((char*)&x2, sizeof(x2));
    outF.write((char*)&y2, sizeof(double)); // it is possible so

    // 4. Close the file
    outF.close();
  }

  bool ReadFromFile(const char* filename)
  {
    // 1. Create an instance of inF that is associated with the file filename
    ifstream inF(filename, ios::in | ios::binary);

    // 2. Checking if a file was opened successfully
    if (!inF.is_open()) return false;

    // 3. Reading the line coordinates into temporary variables
    double x1, y1, x2, y2;

    // the reading order of the fields must match the writing order
    inF.read((char*)&x1, sizeof(double));
    inF.read((char*)&y1, sizeof(double));
    inF.read((char*)&x2, sizeof(x2));
    inF.read((char*)&y2, sizeof(y2));

    // 4. Writing coordinates to the internal fields of the current object
    p1.SetXY(x1, y1);
    p2.SetXY(x2, y2);

    // 5. Close the file
    inF.close();
  }
};

void main()
{
  // 1. Create the first line and output it
  Line L1(2.5, 3.8, 4.1, 7.9);
  L1.Print("L1");

  // 2. Create a second line and output it
  Line L2(1.0, 1.0, 2.0, 2.0);
  L2.Print("L2");

  // 3. Write first line to file "line.bin"
  L1.SaveToFile("line.bin");

  // 4. Read data from "line.bin" file and write it to L2 object
  L2.ReadFromFile("line.bin");

  // 5. Display the value of L2 object
  L2.Print("L2 <= L1:");
}

Program result

L1
p1 => x = 2.5, y = 3.8
p2 => x = 4.1, y = 7.9
-----------------------
L2
p1 => x = 1, y = 1
p2 => x = 2, y = 2
-----------------------
L2 <= L1:
p1 => x = 2.5, y = 3.8
p2 => x = 4.1, y = 7.9
-----------------------

 


Related topics