3

I'm storing my class object in the binary file but I'm getting weird results when I load the data.
Following Code is Loading and Saving Data:

#include <iostream>
#include <fstream>
#include <memory>
#include <string>
#include <sstream>
using namespace std;

template <class C>
void Load(const char fileName[], C& obj)
{
    ifstream in;
    in.open(fileName, ios::in | ios::binary);
    in.read(reinterpret_cast<char*>(addressof(obj)), sizeof(obj));
    in.close();
    return;
}

template <class T>
void Save(const char fileName[], T obj)
{
    ofstream out;
    out.open(fileName, ios::out | ios::binary);
    out.write(reinterpret_cast<char const*>(addressof(obj)), sizeof(obj));
    stringstream ss;
    out.close();
    return;
}

class Contact {
public:
    int CompareTo(Contact obj)
    {
        return 1;
    }
    string ss;
    int rollNum;
};

class Data {
public:
    Data()
    {
    }
    Contact arr[10];
};

int main()
{
    const char fileName[] = "ContactMG.dat";
    /*
     Data *T = new Data();
    
     for(int i=0;i<10;i++)
          T->arr[i].ss = "fahad";
       Save(fileName , *T);
    */

    Data* d = new Data();
    Load(fileName, *d);
    for (int i = 0; i < 10; i++)
        cout << d->arr[i].ss << endl;
}

/*
 Console outPut:
ⁿx

 p²x
   σß╥Z∙
  ░▒▓│┤
   >
☺Y╩
░‼╩

*/

/* Binary File
   @®     ®     ®     
*/

I want to ask how I can store this object in the binary file and load it?

I'm pretty sure the problem is with string but I don't know how to fix it! I have already known to store strings in binary files but don't know how to store class objects which have string in it

Steve Summit
  • 45,437
  • 7
  • 70
  • 103
zain ul din
  • 1
  • 1
  • 2
  • 23
  • 5
    This can't possibly work since `Contact` is not a POD type. A `std::string` will contain pointers to the actual data. When you saved this you saved the pointers not the data. – drescherjm Dec 18 '21 at 17:44
  • If you save a pointer as binary to a file, when reading it in from the file it will contain a garbage pointer value. Pointers do not round trip like that. A `std::string` is a smart pointer with string semantics. – Eljay Dec 18 '21 at 17:45
  • It does not work I have already tried this method. There is issue with reinterpret_cast in think – zain ul din Dec 18 '21 at 17:47
  • Yes you can't use reinterpret_cast<> in this case for the reason I mentioned in my first comment. – drescherjm Dec 18 '21 at 17:50
  • so what'll be the solution? will you provide me right code or correct my code? – zain ul din Dec 18 '21 at 17:52
  • Depending on your situation you may be able to replace `string ss;` with a fixed size character array. – drescherjm Dec 18 '21 at 17:53
  • Related to your problem: [https://stackoverflow.com/questions/59757242/can-i-serialize-a-class-by-casting-it-to-a-char](https://stackoverflow.com/questions/59757242/can-i-serialize-a-class-by-casting-it-to-a-char) – drescherjm Dec 18 '21 at 17:55
  • i know I can replace string with a char array but is there any alternative of reinterpret_cast<> – zain ul din Dec 18 '21 at 17:56
  • 1
    An alternate is this question which shows you how to serialize a class containing a std::string : [https://stackoverflow.com/questions/7046244/serializing-a-class-which-contains-a-stdstring](https://stackoverflow.com/questions/7046244/serializing-a-class-which-contains-a-stdstring) – drescherjm Dec 18 '21 at 17:57
  • Computer files don't contain C++ "objects", they only contain _bytes_. The first step to serializing any C++ object to a file is to first decide what bytes need to be written in order to represent the object. – Drew Dormann Dec 18 '21 at 17:57
  • do you have any advice to store singleton class in the file easily? – zain ul din Dec 18 '21 at 17:59
  • 2
    For development purposes, I recommend writing & reading to a *text* format first. Possibly a structured format, like XML or JSON or YAML or one of your own devising that is suitable for your purposes. Get that working. Then consider if it is worth it to have a binary format for reading & writing. – Eljay Dec 18 '21 at 18:00
  • 1
    ***do you have any advice to store singleton class in the file easily?*** Use a text format and JSON or XML and a library for that. – drescherjm Dec 18 '21 at 18:02
  • we can't make a generic method for XML or JSON in c++ that's why I'm using binary files – zain ul din Dec 18 '21 at 18:02
  • @drescherjm will you provide me a link to the libaray? – zain ul din Dec 18 '21 at 18:03
  • Using a binary format will make your efforts orders of magnitude more difficult during development and to maintain. Get text working first, then get binary working. – Eljay Dec 18 '21 at 18:04
  • https://github.com/open-source-parsers/jsoncpp or https://github.com/nlohmann/json – drescherjm Dec 18 '21 at 18:04
  • 2
    In addition to Eljay and drescherjm comments: Start with YAML / JSON; if you need to stay with a binary format, e.g. because of performance reasons, you might want to have a look at Google's Protocol Buffers: https://developers.google.com/protocol-buffers – Andreas Florath Dec 18 '21 at 19:36
  • @AndreasFlorath Thanks I want to stay with binary files – zain ul din Dec 18 '21 at 19:48

1 Answers1

5

I would introduce a new level of indirection, i.e. functions from_binary and to_binary, and implement your Load and Store in terms of those:

template <class C>
bool Load(const char fileName[], C& obj) {
    if (ifstream in{fileName, ios::in | ios::binary}) {
        from_binary(in, obj);
        return true;
    }

    return false;
}

template <class T>
bool Save(const char fileName[], T obj) {
    if (ofstream out{fileName, ios::out | ios::binary}) {
        to_binary(out, obj);
        return true;
    }

    return false;
}

For POD data types, from_binary and to_binary will just do what you already did in Load/Store (beware, however: pointers are PODs but saving an address is pretty much meaningless):

template <class T, typename = enable_if_t<is_pod_v<T>>>
void from_binary(ifstream& in, T& obj) {
    in.read(reinterpret_cast<char*>(addressof(obj)), sizeof(obj));
}

template <class T, typename = enable_if_t<is_pod_v<T>>>
void to_binary(ofstream& out, T const& obj) {
    out.write(reinterpret_cast<char const*>(addressof(obj)), sizeof(obj));
}

As pointed out in the comments, std::string is not a POD type. I'm going to serialize it by saving the character count and then the actual characters:

void from_binary(ifstream& in, string& str) {
    std::size_t stringSize{0};
    from_binary(in, stringSize);

    str.reserve(stringSize);
    for (size_t i = 0; i != stringSize; ++i) {
        char ch{};
        in.read(&ch, 1);
        str.push_back(ch);
    }
}

void to_binary(ofstream& out, string const& str) {
    auto const stringSize = str.size();
    to_binary(out, stringSize);

    auto const* cStr = str.c_str();
    out.write(cStr, stringSize);
}

Also, I'm going to serialize/deserialize an array by calling to_binary/from_binary on each element of the array:

template <class T, size_t N>
void from_binary(ifstream& in, T (&obj)[N]) {
    for (auto& elem : obj) from_binary(in, elem);
}

template <class T, size_t N>
void to_binary(ofstream& out, T const (&obj)[N]) {
    for (auto const& elem : obj) to_binary(out, elem);
}

The above functions are enough to implement from_binary and to_binary for your Contact and Data classes:

#include <fstream>
#include <iostream>
#include <string>

using namespace std;

template <class T, typename = enable_if_t<is_pod_v<T>>>
void from_binary(ifstream& in, T& obj) {
    in.read(reinterpret_cast<char*>(addressof(obj)), sizeof(obj));
}

template <class T, typename = enable_if_t<is_pod_v<T>>>
void to_binary(ofstream& out, T const& obj) {
    out.write(reinterpret_cast<char const*>(addressof(obj)), sizeof(obj));
}

void from_binary(ifstream& in, string& str) {
    std::size_t stringSize{0};
    from_binary(in, stringSize);

    str.reserve(stringSize);
    for (size_t i = 0; i != stringSize; ++i) {
        char ch{};
        in.read(&ch, 1);
        str.push_back(ch);
    }
}

void to_binary(ofstream& out, string const& str) {
    auto const stringSize = str.size();
    to_binary(out, stringSize);

    auto const* cStr = str.c_str();
    out.write(cStr, stringSize);
}

template <class T, size_t N>
void from_binary(ifstream& in, T (&obj)[N]) {
    for (auto& elem : obj) from_binary(in, elem);
}

template <class T, size_t N>
void to_binary(ofstream& out, T const (&obj)[N]) {
    for (auto const& elem : obj) to_binary(out, elem);
}

template <class C>
bool Load(const char fileName[], C& obj) {
    if (ifstream in{fileName, ios::in | ios::binary}) {
        from_binary(in, obj);
        return true;
    }

    return false;
}

template <class T>
bool Save(const char fileName[], T obj) {
    if (ofstream out{fileName, ios::out | ios::binary}) {
        to_binary(out, obj);
        return true;
    }

    return false;
}

class Contact {
   public:
    int CompareTo(Contact obj) { return 1; }
    string ss;
    int rollNum;
};

void from_binary(ifstream& in, Contact& obj) {
    from_binary(in, obj.ss);
    from_binary(in, obj.rollNum);
}

void to_binary(ofstream& out, Contact const& obj) {
    to_binary(out, obj.ss);
    to_binary(out, obj.rollNum);
}

class Data {
   public:
    Data() {}
    Contact arr[10];
};

void from_binary(ifstream& in, Data& obj) { from_binary(in, obj.arr); }

void to_binary(ofstream& out, Data const& obj) { to_binary(out, obj.arr); }

int main() {
    const char fileName[] = "ContactMG.dat";

    {
        Data data;

        auto const contactCount = sizeof(data.arr) / sizeof(data.arr[0]);
        for (size_t c = 0; c != contactCount; ++c) {
            data.arr[c].ss = "some name " + to_string(c);
            data.arr[c].rollNum = c;
        }

        Save(fileName, data);
    }

    {
        Data data;
        Load(fileName, data);

        for (auto const& contact : data.arr)
            cout << "Contact: rollNum=" << contact.rollNum
                 << ", ss=" << contact.ss << '\n';
    }
}

Output:

Contact: rollNum=0, ss=some name 0
Contact: rollNum=1, ss=some name 1
Contact: rollNum=2, ss=some name 2
Contact: rollNum=3, ss=some name 3
Contact: rollNum=4, ss=some name 4
Contact: rollNum=5, ss=some name 5
Contact: rollNum=6, ss=some name 6
Contact: rollNum=7, ss=some name 7
Contact: rollNum=8, ss=some name 8
Contact: rollNum=9, ss=some name 9

Although this may solve your particular issue, the number of overloads you'll need for from_binary and to_binary will grow very rapidly as your project grows. So I'd definetely check if there's a more comprehensive (and well tested) library solution out there.

paolo
  • 2,345
  • 1
  • 3
  • 17
  • Hey thanks, paolo please consider up vote if you find this answer helpful so it can help others – zain ul din Apr 19 '22 at 00:00
  • one more question can you share some c++ learning sites where I can learn c++ like you – zain ul din Apr 19 '22 at 00:05
  • There are many great resources you may look at, e.g. any book from Stroustrup will do. I also find Scott Mayers books very clear. I recommend you keep [cppreference](https://en.cppreference.com/w/) and the [C++ Core Guidelines](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines) always open in your brwser. The [CppCon channel](https://www.youtube.com/user/CppCon) on YouTube is another great source of information: the _Back To The Basics_ videos are expecially useful for beginners (and often for experienced developers too). – paolo Apr 19 '22 at 20:13
  • Thanks, @paolo. can you check this question also please vote this up if you find this helps many people vote down due to in-depth questions and that's why I got banned from question ask https://stackoverflow.com/questions/70997431/how-to-check-c-pointer-pointing-to-invalid-memory-address – zain ul din Apr 22 '22 at 07:25