Error writing objects of a class to file

Hello everybody.

I am learning C ++ programming by myself, watching YouTube videos about C ++ programming and reading some free books that I download from the web.

This is my first time on this forum and I'm not sure if I can ask questions to find a bug. If not, I apologize.

I am making a program in OOP to save the objects of a class in a file. The second part of the program reads this file and prints it to the console.
My problem lies with variables (attributes) type string, because when writing these variables to the file and then reading them, it does not read what the file is supposed to contain. With other types of variables (int) the program works fine. I have the problem with string variables. Next I show you the program and what it displays on the console.

Program:

// Create athletes object file
#include <iostream>
#include <fstream>
using namespace std;

// Class to define the properties
class Athletes {
public: // Instance variables
string AName; // athlete name
int AAge; // athlete age
public: // Methods
int createFile(); // Function to create file
int readFile(); // Function to read file
};

int Athletes::createFile() { // Athletes: create file rutine

ofstream AFile; // output file

AFile.open("/sdcard/Download/C_C++/Athletes.txt", ios::out);
printf ("Open output status: %d\n\n", AFile.rdstate());

Athletes a1;

a1.AName = "Peter"; a1.AAge=21;
AFile.write((char*)&a1, sizeof(a1));
printf ("Write status: %d Fields: %s %d\n",
AFile.rdstate(), a1.AName.c_str(), a1.AAge);

a1.AName = "John"; a1.AAge=23;
AFile.write((char*)&a1, sizeof(a1));
printf ("Write status: %d Fields: %s %d\n",
AFile.rdstate(), a1.AName.c_str(), a1.AAge);

cout << endl;
AFile.flush();
AFile.close();

return 0;
}

int Athletes::readFile() { // Athletes: read file rutine

ifstream AFile; // read file

AFile.open("/sdcard/Download/C_C++/Athletes.txt", ios::in);
printf ("Open input status: %d\n\n", AFile.rdstate());

Athletes a1;

AFile.read((char*)&a1, sizeof(a1));
printf ("Read status : %d ", AFile.rdstate());
printf ("athlete's name: %s age: %d\n", a1.AName.c_str(), a1.AAge);

AFile.read((char*)&a1, sizeof(a1));
printf ("Read status : %d ", AFile.rdstate());
printf ("athlete's name: %s age: %d\n", a1.AName.c_str(), a1.AAge);

cout << endl;
AFile.close();
printf ("Read status : %d\n", AFile.rdstate());

return 0;
}

int main()
{
// Creating object of the class
Athletes obj;

// Inputting the data
obj.createFile();
obj.readFile();
printf ("Program ended");

return 0;
}

Console output:
Open output status: 0
Write status: 0 Fields: Peter 21
Write status: 0 Fields: John 23
Open input status: 0
Read status : 0 athlete's name: John age: 21
Read status : 0 athlete's name: John age: 23
Read status : 0
Program ended

For some unknown reason it cannot read the first field of the first record. The one that starts with Peter.

I would really appreciate if someone can help me with this problem in the program.
You can't write a std::string variable to a file and expect to read it back.
std::string is not a POD (Plain Old Data) class.
std::string may contain a memory pointer to heap memory (outside the class).
What may get written to the file if you treat syd::string as a POD class is the memory pointer,

PLEASE ALWAYS USE CODE TAGS (the <> formatting button) when posting code.
It makes it easier to read your code and also easier to respond to your post.
http://www.cplusplus.com/articles/jEywvCM9/
Hint: You can edit your post, highlight your code and press the <> formatting button.
You need to choose a storage format for the string data.
There are three common choices:

1. Use a fixed size buffer
   Pro: simple
        allows "random access" to records (i.e., all records are the same size)
   Con: wastes space
2. Store the string data complete with a '\0' (or possibly other) terminator.
   Pro: simple
   Con: don't know the size of the string until you've read the last char
        no random access
        can't store the terminator ('\0') char in the string
3. Store the size of the string first, then the string data.
   Pro: know the size of the string so it storage can be allocated before
        the char data is read
   Con: no random access

You should open the files in "binary" mode so that any "text-mode" alterations to the raw stream data are turned off.

Here's an example of storing the size first:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <iostream>
#include <fstream>
#include <string>
using namespace std;    

int main() {
    const char* const Filename = "somedat.bin";

    {
        string s = "hello world";
        ofstream fout(Filename, fout.binary);
        size_t sz = s.size();
        fout.write((char*)&sz, sizeof sz);
        fout.write((char*)&s[0], sz);
        // fout automatically flushed/closed at end of scope
    }

    {
        string s;
        ifstream fin(Filename, fin.binary);
        size_t sz;
        fin.read((char*)&sz, sizeof sz);
        s.resize(sz);
        fin.read((char*)&s[0], sz);
        cout << s << '\n';
        // fin automatically flushed/closed at end of scope
    }
}

@OP Before you do some filing, some suggested changes to the Athlete class. Note that std:: sprayed everywhere (as appropriate) is a better alternative to using namespace std

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <iostream>

// .h (header file)
class Athlete
{
private:
    std::string m_name{"???"};
    int m_age{-99} ;

public:
    Athlete();
    Athlete(std::string, int);
    ~Athlete(){};

    std::string getName();
    int getAge();
};

// .cpp (implementation file)
Athlete::Athlete(){}
Athlete::Athlete(std::string name, int age){m_name = name; m_age = age;}
std::string Athlete::getName(){return m_name;}
int Athlete::getAge(){return m_age;}

int main()
{
    Athlete empty;
    std::cout << empty.getName() << ' ' << empty.getAge() << '\n';

    Athlete full( "Jack", 32);
    std::cout << full.getName() << ' ' << full.getAge() << '\n';

    return 0;
}



??? -99
Jack 32
Program ended with exit code: 0
You might have success if you look up using json or xml format. Both are easy to use, and were created for storing data from objects.

They both have the upside/downside that when they are saved they will be human readable, so it's possible to modify the data in notepad without the program running. If that's a problem then a little encryption might be fun to play with.

They both allow for storing data for multiple objects in the same file, but if you are going to save a lot of different objects, then I'd recommend xml for it's robustness.
Last edited on
@OP Extending that to filing:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#include <iostream>
#include <fstream>

// .cpp (header file)
class Athlete
{
private:
    std::string m_name{"???"};
    int m_age{-99} ;

public:
    Athlete();
    Athlete(std::string, int);
    ~Athlete(){};

    std::string getName();
    int getAge();

    void setName(std::string);
    void setAge(int);

    std::string to_string();
};

// .cpp (implementation file)
Athlete::Athlete(){}
Athlete::Athlete(std::string name, int age){m_name = name; m_age = age;}

std::string Athlete::getName(){return m_name;}
int Athlete::getAge(){return m_age;}

void Athlete::setName(std::string name){m_name = name; }
void Athlete::setAge(int age){m_age = age;}

std::string Athlete::to_string(){return (m_name + " " + std::to_string(m_age)); }


int main()
{
    Athlete empty;
    std::cout << empty.getName() << ' ' << empty.getAge() << '\n';

    Athlete full( "Jack", 32);
    std::cout << full.to_string() << '\n';

    Athlete a1("Peter", 21), a2("John",23), a3("Mary", 22);

    // CREATE FILE AND SAVE ATHLETES
    std::string file_name{"team.txt"};
    std::ofstream output(file_name);
    if( !output )
       return -100;

    output << empty.to_string() << '\n';
    output << full.to_string() << '\n';
    output << a1.to_string() << '\n';
    output << a2.to_string() << '\n';
    output << a3.to_string() << '\n';

    output.close();


    // READ ALL ATHLETES FROM FILE
    Athlete temp;
    std::ifstream input(file_name);
    if( !input )
        return -100;

    std::string tempName{"?"};
    int tempAge{0};

    while(input >> tempName >> tempAge)
    {
        temp.setName(tempName);
        temp.setAge(tempAge);

        std::cout << temp.to_string() << '\n';
    }
    input.close();

    return 0;
}



??? -99
Jack 32
??? -99
Jack 32
Peter 21
John 23
Mary 22
Program ended with exit code: 0
@javier55 For the requirement, I'd be looking to dutch's option 1).

Possibly:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
#include <iostream>
#include <fstream>
#include <cstdio>

constexpr size_t maxStr {200};

// Class to define the properties
class Athletes {
private:
	char AName[maxStr] {};	// athlete name
	int AAge {};		// athlete age

public: // Methods
	Athletes() {}
	Athletes(const std::string& nam, int age) : AAge(age) {
		std::snprintf(AName, maxStr, "%s", nam.c_str());
	}

	void setName(const std::string& nam) { snprintf(AName, maxStr, "%s", nam.c_str()); }
	std::string getName() const { return AName; }
	void setAge(int age) { AAge = age; }
	int getAge() const { return AAge; }
	static int createFile();
	static int readFile();
};

int Athletes::createFile() {
	std::ofstream AFile (/*"/sdcard/Download/C_C++/Athletes.txt"*/ "Athletes.txt");

	if (!AFile)
		return 0;

	Athletes a1("Peter", 21);

	AFile.write((char*)&a1, sizeof(a1));

	a1.setName("John");
	a1.setAge(23);
	AFile.write((char*)&a1, sizeof(a1));

	return 1;
}

int Athletes::readFile() {

	std::ifstream AFile (/*"/sdcard/Download/C_C++/Athletes.txt"*/ "Athletes.txt");

	if (!AFile)
		return 0;

	for (Athletes a1; AFile.read((char*)&a1, sizeof(a1)); )
		std::cout << "athlete's name: " << a1.getName() << ". age: " << a1.getAge() << '\n';

	std::cout << '\n';
	return 1;
}

int main()
{
	Athletes obj;

	// Inputting the data
	if (obj.createFile()) {
		if (!obj.readFile())
			std::cout << "Cannot open input file\n";
	} else
		std::cout << "Cannot open output file\n";
}

Last edited on
what is happening:

you are storing a pointer in the file. When you try to write an object with a pointer in it directly, you get the pointer, not the data. Pointers are just a location in ram where you data was this time, but your data won't be there next time... it will not work.

the above answers all do one thing in common: they write the data, not the pointer, to the file.

you will run into this for every c++ container (vector, string, list, etc). They all use pointers internally and cann't be directly written to a file, you have to go to the data and write that, and when reading, repopulate the container from the data.

random access means you can go to the nth record directly: the file is like an array, each location the same size, so you can jump around in it at the object/class/record level, and you can overwrite it easier too(to me this is a bigger deal: changing 1 byte and having to write 5g back to disk is poor), because you can change just 1 record without moving the others around. Random access is highly desirable :) you can get there by padding each field so you can just .read or .write an object directly, or you can pad at the record level and parse the record data each time.
Last edited on
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
#include <iostream>
#include <fstream>

class Athlete
{
private:
    std::string m_name{"???"};
    int m_age{-99} ;

public:
    Athlete(){}
    Athlete(std::string name, int age){m_name = name; m_age = age;}
    ~Athlete(){};

    std::string getName(){return m_name;}
    int getAge(){return m_age;}

    void setName(std::string name){m_name = name; }
    void setAge(int age){m_age = age;}

    std::string to_string(){return (m_name + " " + std::to_string(m_age)); }
};


class AthleteFile
{
private:
    std::string m_file_name;

public:
    AthleteFile( std::string file_name)
    {
        m_file_name = file_name;
    }

    void add_to_file(Athlete athlete)
    {
        std::ofstream file(m_file_name, std::ios::app);
        file << athlete.to_string() << '\n';
    }

    void read_from_file()
    {
        std::ifstream file(m_file_name);

        Athlete temp;
        std::string name;
        int age{0};

        while(file >> name >> age)
        {
            temp.setName(name);
            temp.setAge(age);
            std::cout << temp.to_string() << '\n';
        }
    }
};


int main()
{
    Athlete empty;
    std::cout << empty.getName() << ' ' << empty.getAge() << '\n';

    Athlete full( "Jack", 32);
    std::cout << full.to_string() << '\n';

    Athlete a1("Peter", 21), a2("John",23), a3("Mary", 22);

    AthleteFile athFile_1("athlete_team_1.txt");
    AthleteFile athFile_2("athlete_team_2.txt");

    athFile_1.add_to_file(empty);

    athFile_2.add_to_file(full);

    athFile_1.add_to_file(a1);
    athFile_1.add_to_file(a2);
    
    athFile_2.add_to_file(a3);

    std::cout << "Contents of athFile_2.txt\n";
    athFile_2.read_from_file();


    std::cout << "Contents of athFile_1.txt\n";
    athFile_1.read_from_file();

    return 0;
}



??? -99
Jack 32
Contents of athFile_2.txt
Jack 32
Mary 22
Contents of athFile_1.txt
??? -99
Peter 21
John 23
Program ended with exit code: 0
Thanks everyone for your help. I changed the program from std :: string to char and it worked fine. I also used fixed size buffer as one of you suggested (I think it was dutch). I didn't know about POD classes until now, so I have to keep studying more. Anyway I will analyze all your comments and do tests. Thanks.

This is how my program looks now:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
// Create athletes object file
// Prueba usando char[20] en vez de string
#include <iostream>
#include <fstream>
#include <string>
using namespace std;

// Class to define the properties
class Athletes {
    public:                                  // Instance variables
       char   AName[20];           // athlete name
       int      AAge;                      // athlete age
    public:                                  // Methods
       int      createFile();           // Function to create file
       int      readFile();              // Function to read file
};

int Athletes::createFile() {      // Athletes: create file rutine

    ofstream AFile;                   // output file

    AFile.open("/sdcard/Download/C_C++/Athletes.txt", ios::out);
    printf ("Open output status: %d\n\n", AFile.rdstate());

    Athletes a1;

    strcpy(a1.AName,"Peter");     a1.AAge=21;
    AFile.write((char*)&a1, sizeof(a1));
    printf ("Write status: %d Fields: %s %d\n",
                 AFile.rdstate(), a1.AName, a1.AAge);

    memcpy(a1.AName,"John", 9);   a1.AAge=23;  // In these line I test with number of character to copy
    AFile.write((char*)&a1, sizeof(a1));
    printf ("Write status: %d Fields: %s %d\n",
                 AFile.rdstate(), a1.AName, a1.AAge);
	         
    cout << endl;
    AFile.flush();
    AFile.close();

    return 0;
}

int Athletes::readFile() {         // Athletes: read file rutine

    ifstream AFile;                    // read file

    AFile.open("/sdcard/Download/C_C++/Athletes.txt", ios::in);
    printf ("Open input status: %d\n\n", AFile.rdstate());

    Athletes a1;
	
    AFile.read((char*)&a1, sizeof(a1));
    printf ("Read status : %d ", AFile.rdstate());
    printf ("athlete's name: %s age: %d\n", a1.AName, a1.AAge);
	
    AFile.read((char*)&a1, sizeof(a1));
    printf ("Read status : %d ", AFile.rdstate());
    printf ("athlete's name: %s age: %d\n", a1.AName, a1.AAge);
	
    cout << endl;
    AFile.close();
    printf ("Read status : %d\n", AFile.rdstate());

    return 0;
}

int main() {
    // Creating object of the class
    Athletes obj;

    // Processing the data
    obj.createFile();
    obj.readFile();
    printf ("Program ended");

    return 0;
} 


And the output was:
1
2
3
4
5
6
7
8
9
10
11
12
Open output status: 0

Write status: 0 Fields: Peter 21
Write status: 0 Fields: John 23

Open input status: 0

Read status : 0 athlete's name: Peter age: 21
Read status : 0 athlete's name: John age: 23

Read status : 0
Program ended


Thanks again
Last edited on
Topic archived. No new replies allowed.