Make class with constant size. Help!

Pages: 123
I am now trying to put everything together to try to learn how to store the data members of an object I know that I do not follow the typical pair file structure since I have everything in the same file, I only did it now since this is just a test and I want to focus on this first. Here is the code:

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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
//Program for writing and reading data safely to binary file in binary format.
//It used Little Endianness and reads/writes low byte first.

#include <iostream>
#include <fstream>
#include "MyDataTypes.h"

using namespace std;

//Declaring function prototypes for reading
u8 readu8(ifstream&);
u16 readu16(ifstream&);
u32 readu32(ifstream&);
u64 readu64(ifstream&);

//Declaring function prototypes for writing
void writeu8(ofstream&, u8);
void writeu16(ofstream&, u16);
void writeu32(ofstream&, u32);
void writeu64(ofstream&, u64);

//Declaring function prototype for reading string.
string& readstring(ifstream&);

//Declaring function prototype for writing string.
void writestring(ofstream&, string);

using namespace std;

class Human {
public:
	Human(string n = "Default", u8 a = 26): name(n), age(a) {}
	string name;
	u8 age;
	void write(ofstream& fileout) const;
	void read(ifstream& filein);

protected:
private:
};

void Human::write(ofstream& fileout) const {
	writestring(fileout, name);
	writeu8(fileout, age);
}

void Human::read(ifstream& filein) {
	name = readstring(filein);
	age = readu8(filein);
}

int main() {

	ifstream filein("testfile.bin", ios::in | ios::binary);
	filein.close();
	ofstream fileout("testfile.bin", ios::out | ios::binary);
	fileout.close();

	Human h("Tobias", 19);
	h.write(fileout);

	system("pause");
	exit(EXIT_SUCCESS);
	return 0;
}

//Defining functions for reading

u8 readu8(ifstream& filein) {
	u8 byte = 0;
	char val = 0;
	filein.open("testfile.bin", ios::in | ios::binary);
	if(!filein){
		cout << "The file could not be opened by function "
			<< "readu8." << endl;
		exit(EXIT_FAILURE);
	}
	filein.read((char*) byte, 1);
	filein.close();
	val = val | byte;
	return val;
}

u16 readu16(ifstream& filein) {
	char bytes[2];
	u16 val;
	filein.open("testfile.bin", ios::in | ios::binary);
	if(!filein) {
		cout << "The file could not be opened by function "
			<< "readu16." << endl;
		exit(EXIT_FAILURE);
	}
	filein.read((char*)bytes, 2);
	filein.close();
	val = bytes[0] | (bytes[1] << 8);
	return val;
}

u32 readu32(ifstream& filein) {
	u32 val;
	char bytes[4];
	filein.open("testfile.bin", ios::in | ios::binary);
	if(!filein) {
		cout << "The file could not be opened by function "
			 << "readu32." << endl;
		exit(EXIT_FAILURE);
	}
	filein.read((char*) bytes, 4);
	filein.close();
	val = bytes[0] | (bytes[1] << 8) | (bytes[2] << 16) | (bytes[3] << 24);
	return val;
}

u64 readu64(ifstream& filein) {
	u64 val;
	char bytes[8];
	filein.open("testfile.bin", ios::in | ios::binary);
	if(!filein) {
		cout << "The file could not be opened by function "
			 << " readu64." << endl;
		exit(EXIT_FAILURE);
	}
	filein.read((char*) bytes, 8);
	filein.close();
	val = bytes[0] | (bytes[1] << 8) | (bytes[2] << 16) | (bytes[3] << 24)
		| (static_cast<u64>(bytes[4])) << 32 | (static_cast<u64>(bytes[5])) << 40 | (static_cast<u64>(bytes[6])) << 48 | (static_cast<u64>(bytes[7])) << 56;
	return val;
}

//Defining functions for writing binary data.

void writeu8(ofstream& fileout, u8 val) {
	char byte;
	byte = val;
	fileout.open("testfile.bin", ios::out | ios::binary);
	if(!fileout) {
		cout << "THe file could not be opened by function "
			<< "writeu8." << endl;
		exit(EXIT_FAILURE);
	}
	fileout.write((char*) byte, 1);
	fileout.close();
}

void writeu16(ofstream& fileout, u16 val) {
	char bytes[2];
	bytes[0] = (val & 0xFF);
	bytes[1] = (val & 0xFF00) >> 8;
	fileout.open("testfile.bin", ios::out | ios::binary);
	if(!fileout) {
		cout << "The file could not be opened by function "
			 << "writeu16." << endl;
		exit(EXIT_FAILURE);
	}
	fileout.write((char*) bytes, 2);
	fileout.close();
}

void writeu32(ofstream& fileout, u32 val) {
	char bytes[4];
	bytes[0] = (val & 0x000000FF);
	bytes[1] = (val & 0x0000FF00) >> 8;
	bytes[2] = (val & 0x00FF0000) >> 16;
	bytes[3] = (val & 0xFF000000) >> 24;
	fileout.open("testfile.bin", ios::out | ios::binary);
	if(!fileout) {
		cout << "The file could not be opened by function " 
			<< "writeu32." << endl;
		exit(EXIT_FAILURE);
	}
	fileout.write((char*) bytes, 4);
	fileout.close();
}

void writeu64(ofstream& fileout, u64 val) {
	char bytes[8];
	fileout.open("testfile.bin", ios::out | ios::binary);
	bytes[0] = (val & 0xFF);
	bytes[1] = (val & 0xFF00) >> 8;
	bytes[2] = static_cast<u8> (val & 0x0000000000FF0000) >> 16;
	bytes[3] = static_cast<u8> (val & 0x00000000FF000000) >> 24;
	bytes[4] = static_cast<u8> (static_cast<u64> (val & 0x0000000FF0000000) >> 32); 
	bytes[5] = static_cast<u8> (static_cast<u64> (val & 0x0000FF0000000000) >> 40);
	bytes[6] = static_cast<u8> (static_cast<u64> (val & 0x00FF000000000000) >> 48);
	bytes[7] = static_cast<u8> (static_cast<u64> (val & 0xFF00000000000000) >> 56);
	if(!fileout) {
		cout << "The file could not be opened by function "
			 << "writeu64." << endl;
		exit(EXIT_FAILURE);
	}
	fileout.write((char*) bytes, 8);
	fileout.close();
}

//Defining function for writing strings.

void writestring(ofstream& fileout, string str) {
	u32 len = str.length();
	fileout.open("testfile.bin", ios::out | ios::binary);
	if(!fileout) {
		cout << "The function could not be opened by function "
			 << "writestring." << endl;
		exit(EXIT_FAILURE);
	}
	writeu32(fileout, len);
	fileout.write(str.c_str(), len);
	fileout.close();
}

//Defining function for reading string

string& readstring(ifstream& filein) {
	u32 len = readu32(filein);
	char* buffer = new char[len];
	filein.open("testfile.bin", ios::in | ios::binary);
	if(!filein) {
		cout << "The file could not be opened by function " 
			<< "readstring." << endl;
		exit(EXIT_FAILURE);
	}
	filein.read(buffer, len);
	filein.close();
	string str(buffer, len);
	delete[] buffer;
	return str;
}


I am trying to make the Human object "h", to write the data members to the binary file "testfile.bin". The problem is that after running the program, the file is still empty, I have opened it with both the hex exitor and notepad to try to see anything but no data has been written to it.

What is wrong with my code? Why does it not write anything?

Btw, I changed the return type of readstring to a reference of a string, since that is much more efficient so that not entire objects have to be returned right? Read that early in my book.
Last edited on
1
2
3
4
5
	ofstream fileout("testfile.bin", ios::out | ios::binary);
	fileout.close();  // <- you are closing fileout here

	Human h("Tobias", 19);
	h.write(fileout); // <-  can't write to a file that's closed.  This write is failing 




Btw, I changed the return type of readstring to a reference of a string, since that is much more efficient so that not entire objects have to be returned right?


It can be in theory. In practice, though, compilers are very good at optimizing object copies away, so you typically do not have to worry about that.

The bigger issue is that you are returning a reference to a local object, which simply will not work. readstring returns a reference to the 'str' string, but the 'str' string dies as soon as the function exits, so the reference immediately becomes bad (it refers to an object that no longer exists).

So no, that's a bad change. Change it back to return a full object.
Well, I know that the file is closed in the beginning of the main function, but I open the file again in the later functions that are called through "write", so why does it not work when the other functions open it?

I remember that now that I should not have returned a reference since it returns an "empty" reference like you said. The book emphazised that but I forgot that :O. I have put so much focus on this now the latest days so I guess that I'll have to go back and reftresh that stuff again :).

Thanks very much for the reply.
Last edited on
Changed main function now:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int main() {

	ifstream filein("testfile.bin", ios::in | ios::binary);
	ofstream fileout("testfile.bin", ios::out | ios::binary);

	Human h("Tobias", 19);
	h.write(fileout);

	filein.close();
	fileout.close();
	system("pause");
	exit(EXIT_SUCCESS);
	return 0;
}


It still does not work. Here is the output, which seems fine to me:


'BinaryFileWriting.exe': Loaded 'C:\Users\Zerpent\Documents\Visual Studio 2010\Projects\BinaryFileWriting\Debug\BinaryFileWriting.exe', Symbols loaded.
'BinaryFileWriting.exe': Loaded 'C:\Windows\SysWOW64\ntdll.dll', Symbols loaded (source information stripped).
'BinaryFileWriting.exe': Loaded 'C:\Windows\SysWOW64\kernel32.dll', Symbols loaded (source information stripped).
'BinaryFileWriting.exe': Loaded 'C:\Windows\SysWOW64\KernelBase.dll', Symbols loaded (source information stripped).
'BinaryFileWriting.exe': Loaded 'C:\Windows\SysWOW64\msvcp100d.dll', Symbols loaded (source information stripped).
'BinaryFileWriting.exe': Loaded 'C:\Windows\SysWOW64\msvcr100d.dll', Symbols loaded (source information stripped).
The program '[5116] BinaryFileWriting.exe: Native' has exited with code 1 (0x1).


Although, the "testfile.bin" binary file is still empty after running it.
Now you're trying to open the file twice.

1
2
	ifstream filein("testfile.bin", ios::in | ios::binary);  // opens the file for reading
	ofstream fileout("testfile.bin", ios::out | ios::binary);  // fails because the file is already open for reading. 


To open a file with write permissions, generally the file cannot be opened by anything else, or the open will fail. Likewise, once a file is opened for writing, nothing else will be able to open it. Write access typically needs to be exclusive.

(there are ways arond this, but that's a whole other topic).

Stop trying to have fileout and filein there at the same time. Do your writing with fileout, then when it's done, make filein and do your reading (or vice versa). Stick to one at a time.
Ok I see, thank you.
There is still a problem, if you look in the full code above, a failbit-/badbit flag was set in the fileout ofstream, in the writestringfunction(). It can not be the problem that you explained before that I open the file with more than one stream at once, I have already fixed that by now. I suspect that the argument that I send to Human::write() functions might cause the problem, since it is a reference to an ofstream that is being sent through several functions which probably is empty when the writestring() function is called. Could that be the case?

I changed so that it sends a ofstream object instead of a reference to fix the problem above (if that even is the problem). But then I get this error:


1>------ Build started: Project: BinaryFileWriting, Configuration: Debug Win32 ------
1>  Main.cpp
1>d:\microsoft visual studio 10.0\vc\include\fstream(1116): error C2248: 'std::basic_ios<_Elem,_Traits>::basic_ios' : cannot access private member declared in class 'std::basic_ios<_Elem,_Traits>'
1>          with
1>          [
1>              _Elem=char,
1>              _Traits=std::char_traits<char>
1>          ]
1>          d:\microsoft visual studio 10.0\vc\include\ios(176) : see declaration of 'std::basic_ios<_Elem,_Traits>::basic_ios'
1>          with
1>          [
1>              _Elem=char,
1>              _Traits=std::char_traits<char>
1>          ]
1>          This diagnostic occurred in the compiler generated function 'std::basic_ofstream<_Elem,_Traits>::basic_ofstream(const std::basic_ofstream<_Elem,_Traits> &)'
1>          with
1>          [
1>              _Elem=char,
1>              _Traits=std::char_traits<char>
1>          ]
========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========



Sorry for all the problems but I really want to make tis work since I have been trying for so long. I hope that it does not start to bother you. Thanks for all the help.

Btw, I did change to code before this so that I open the file with one stream at the time like you told me in your previous reply, so it should not be that anymore that causes the problem.
Last edited on
You have to pass the ostream by reference. streams cannot be copied. The reference is not your problem here.

Are you still opening/closing the file in every ReadXX/WriteXX function? Because you shouldn't be. Those functions should just use the file they are passed -- they should not be trying to open/close the file themselves.
Finally it works! I can not thank you enough Disch for taking all this time to help just me with this and to put me in the right direction :). I hope that It has not bothered you too much, but at least it was not in vain since I know how everything works now. All I'll need to do is to practice on navigating through the file and then I'll be ready to make some fun progams.

Again thanks, I never thought that I could get all this support from one guy :).

I only have one last question before finnishing this. By doing all this that we have discussed now, how portable will my saves files and programs be now if I take all this in consideration (which I always will). Is it hard to estimate about which OS:s, compilers and machines will be able to run my programs? Or do I have to experiment? I make my programs in Visual C++ 2010 on a Win 7 64 bit machine with an Intel i7 CPU. You can skip answering this if it is not possible but I am just so excited.
Last edited on
I only have one last question before finnishing this. By doing all this that we have discussed now, how portable will my saves files and programs be now if I take all this in consideration


There are other factors to consider when you're talking about overall program portability... so I can't say that any program that uses this code will be portable. Just because one area of the program is portable doesn't mean the entire program will be.

But this file I/O code is 99.99% portable because it uses the standard library (supported everywhere) and makes minimal assumptions about the underlying architecture.

The only assumptions made by this code (that I'm aware of) are:
1) There are 8 bits in 1 byte
2) Signed integers are stored in 2's compliment.

Those 2 assumptions are true on virtually all modern architectures, so they're pretty safe. This is very portable code.

Again thanks, I never thought that I could get all this support from one guy :).


Happy to help.
I just had a look through everything to memorize what has been said. On the first page, where you show how to define the file format, why do you store the version indicator "1" in a 32-bit block, instead of just using 8 bits?

Like this:
01 00 00 00


Instead of:
01


to save 24 bits?
Last edited on
Changing the data size will work. You can write variables of any size as long as you write/read them correctly.

I just used 32-bit for the version number there because it's what I usually do (my version numbers are generally a bit more complex so 8 bits wouldn't be nearly enough for me).
Ok thank you again, I thought before that I had understood everything but it has been a lot to comprehend.
Topic archived. No new replies allowed.
Pages: 123