Reading/writing vector of struct containing vector of struct to/from file

Hi! My name is Marcos, I am a beginner c++ programmer trying to work my way through my first functional program. Thank you in advance for your help!

The program is to collect and store/analyze data regarding struct::properties(vector) containing struct::rooms(vector) which further contain struct::tenants(vector). The data structures initialize properly and can be written to/read during execution.

How would I add functionality for the structures to be written and read from a file into the data structure?

My current program 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
#include "stdafx.h"
#include <iostream>
#include <vector>
#include <string>
#include <stdio.h>
#include <stdlib.h>

using namespace std;

struct Tenant
{
	char fName[10];
	char lName[10];
	int DOB;
	char email[20];
	int phoneNumber;
};

struct Room
{
	char ID[4];
	vector<Tenant> tenants;
};

struct Property
{
	char locName[15];
	vector<Room> rooms;
}; vector<Property> properties(0);

void addProperty()
{
	Property tProperties;
	int count;
	
	cout << "How many properties would you like to add? ";
	cin >> count;

	for (int x = 1; x <= count; x++)
	{
		cout << "Please enter the " << x << " property: ";
		cin >> tProperties.locName;
		properties.push_back(tProperties);
	}

}

void addRoom()
{
	Room tRoom;
	int propertyN;
	int count;

	cout << "Which property would you like to add a room to?: " << endl;
	for (unsigned j = 0; j < properties.size(); j++)
	{
		cout << j << ":" << properties[j].locName << "  ";
	}
	cin >> propertyN;

	cout << "How many rooms would you like to add to " << properties[propertyN].locName << "? ";
	cin >> count;

	for (int i = 1; i <= count; i++)
	{
		cout << "Please enter the " << i << " room: ";
		cin >> tRoom.ID;
		properties[propertyN].rooms.push_back(tRoom);
	}
}

void addTenant()
{
	Tenant tTenant;
	int propertyN;
	int roomN;
	unsigned count;

	/*for (int i = 0; i < properties.size(); i++)
	{
		for (int j = 0; j < properties[i].tenants.size(); j++)
		{
			properties
		}
	}*/

	cout << "Which property would you like to add a tenant to? " << endl;
	for (unsigned j = 0; j < properties.size(); j++)
	{
		cout << j << ":" << properties[j].locName << "  ";
	}
	cin >> propertyN;

	cout << "Which room would you like to add a tenant to? " << endl;
	for (unsigned j = 0; j < properties[propertyN].rooms.size(); j++)
	{
		cout << j << ":" << properties[propertyN].rooms[j].ID << "  ";
	}
	cin >> roomN;

	cout << "How many tenants would you like to add to room " << properties[propertyN].rooms[roomN].ID << "? ";
	cin >> count;

	for (unsigned i = 1; i <= count; i++)
	{
		cout << "Name: ";
		cin >> tTenant.fName;
		properties[propertyN].rooms[roomN].tenants.push_back(tTenant);
	}
}

void listProperties()
{
	for (unsigned i = 0; i < properties.size(); i++)
	{
		int x = i + 1;
		cout << "The " << x << " Property is " << properties[i].locName << endl;
		if (properties[i].rooms.size() == 0)
		{
			cout << "There are no rooms within this property!" << endl;
		}
		for (unsigned j = 0; j < properties[i].rooms.size(); j++)
		{
			cout << "The rooms within are: " << properties[i].rooms[j].ID << endl;
			if (properties[i].rooms[j].tenants.size() == 0)
			{
				cout << "There are no tenants within this room!" << endl;
			}
			for (unsigned k = 0; k < properties[i].rooms[j].tenants.size(); k++)
			{
				cout << "The tenants within are: " << properties[i].rooms[j].tenants[k].fName << endl;
			}
		}
		x += 1;
	};
		

}

int main()
{
        /*read data from file
	FILE *infile;
	errno_t errI;
	struct Property input;

	errI = fopen_s(&infile, "person.dat", "r");
	if (infile == NULL)
	{
		fprintf(stderr, "\nError opening file\n");
		exit (1);
	}

	while (fread(&input, sizeof(struct Property), 1, infile))
		printf("locName = %s", input.rooms);

	fclose(infile);
        */

	addProperty();
	addRoom();
	addTenant();

        /*write data to file
	FILE *outfile;
	errno_t errO;

	errO = fopen_s(&outfile, "person.dat", "w");
	if (outfile == NULL)
	{
		fprintf(stderr, "\nError opend file\n");
		exit(1);
	}

	fwrite(&properties, sizeof(struct Property), 1, outfile);

	if (fwrite != 0)
		printf("contents to file written successfully!\n");
	else
		printf("Error writing file");

	fclose(outfile);
        */

	listProperties();

	cout << "the Absolute Property name is: " << properties[1].locName << endl;

	return (0);
}


Any comments/suggestions/ideas are welcome and very much so appreciated!
Last edited on
in pseudo code, something like this:

--optional but handy, write # of total rooms in file 'header' entry.
for(all the rooms)
{
write # of tenants to file.
write room ID field to the file.
for( all the tenants)
{
write all the fields of tenant struct to the file one by one.
}
}

and when reading the file, you can
- read the header and preallocate a vector of rooms
- loop over all the rooms reading the id and then
-- for each room loop over # of tenants

pretty much the reverse process.

Does this make sense?

the tenant struct can be written in binary mode if you like. But you still need markers in the file for counts of rooms and tenants per room due to vector.
I would recommend you stop trying to use C-file streams and instead use C++ file streams.

I would also suggest you get rid of any and all global variables, such as the one on line #29.

And remember you can't easily write a structure or class that contains non-trivially constructed C++ classes, such as std::string, std::vector, etc.

jonnin Thanks, the # of rooms/# of tenants would indicate how many data blocks to read into the respective objects right? So if I said there were three rooms it would look for 3 clusters of information relating to the rooms vector member before moving on to the next property? Similar with the # of tenants and tenants cluster info?

Is it possible/advisable to make this number a member of the struct instead of definite? For example when writing a property the obj would know how many of vector rooms it will be writing/reading?

jlb The file streams you are referring to are the commented portions for reading/writing right? How would I go about removing the global variable on line 29? When I try not having it the program breaks with error indicating the vector is out of subscript range.
Jonnin I think I may have misinterpreted you by file "header", do you mean a header file with variable or a written file for data interpretation? Would it be advisable to define a key file that contains the # of properties, # of rooms within each and # of tenants within each room to "prime" the vectors for data reading?
I meant you can add a line of extra data at the front of the file with the # of rooms in it.
so your file looks like this roughly (with c++ comments, assuming text file for now)

4 //4 rooms
1 //room id 1
2 //number of tenants for room id 1
blah //tenant info for tenant 1
blah
blah
blah2 //tenant info for tenant 2 of room 1
blah2
blah2
2 //room id 2
3 //number of tenants for id 2
... etc

don't over complicate the thing. Its pretty simple data, one file will do it, with a simple nested structure. You don't even require the # of rooms line, its just 'nice' to have it.
Last edited on
The file streams you are referring to are the commented portions for reading/writing right?

Yes, the commented out horrible C code that you probably found somewhere or another that probably doesn't work correctly.

How would I go about removing the global variable on line 29?

You would move that variable to some function, where it can be passed to and from functions as required.

When I try not having it the program breaks with error indicating the vector is out of subscript range.

There is probably much more information that your compiler is telling you, such as the line number where the problem is being detected. You need to learn to read and understand your compiler's messages, otherwise you'll never be able to fix your problems.

Jonnin Thank you very much! I followed your suggestions and have achieved data input and output, it got a little tricky with the resize in input but switching from using push_back to the loop variables for vector positions got it working. I also made use of some interpretations taken from http://www.cplusplus.com/forum/beginner/86808/

jlbThank you for your help, I have taken your suggestions into mind, the file streams were indeed just some old code I found and was trying/failing. I decided to keep the global definition of properties as it is a universal construct used in every function. Why is it a bad idea to keep any global variables?
Last edited on
Why is it a bad idea to keep any global variables?


The following is a big part of the answer:

I decided to keep the global definition of properties as it is a universal construct used in every function.


Which means that the values can be changed by anything, anywhere, anytime. True this program is very small and perhaps it's not a big deal, but as the program grows you will find remembering where something changes the value will become impossible. Learn to properly pass the variables to and from the functions that need to see and/or modify the variable. Also remember that many of your functions will probably not have any need to change the value, but because the variable is global there is no way you can get the compiler to help you enforce the const of the variable.

Furthermore, compare these:
1
2
3
4
5
6
7
int mult( int x ) {
  return x * snafu;
}

int mult( int x, int y ) {
  return x * y;
}

Which one of them would you use in here:
1
2
3
4
5
6
7
8
int snafu {42};

int main() {
  for ( int foo = 1; foo < 5; ++foo ) {
    // I want to show: foo * (foo+1)
    // and I have to use mult() to do the multiplication
  }
}

If a function uses a global value, then the function depends on that value. Is restricted. Is less flexible.

The latter mult() has no such dependencies. All necessary data arrives via parameters. (You can still call mult(foo,snafu) to get the same result as from the other version.)

Another example:
1
2
3
4
5
6
7
8
9
std::ostream & print( int x ) {
  std::cout << x;
  return std::cout;
}

std::ostream & print( std::ostream& out, int x ) {
  out << x;
  return out;
}

The first print() can output to exactly one stream.
The user can choose the stream that the second print() writes to.


Lets say there is class Foo. Foo has complex constructor and destructor. The program is in (at least) two files:
a.cpp:
1
2
3
4
5
#include "Foo.h"

Foo fubar;

// other code 

b.cpp:
1
2
3
4
5
#include "Foo.h"

Foo snafu;

// other code 

Can you tell off-hand, when the global fubar and snafu are constructed and when they are destructed?

Alternative:
1
2
3
4
5
6
#include "Foo.h"

int main() {
  Foo foo;
  Foo bar;
}

Can you tell off-hand, when the local foo and bar are constructed and when they are destructed?
you can put vector of properties in main and pass it to all functions. This is the 'right' way to do things in most code. Pass by reference (const reference if you don't want to change the values in the functions) and the cost is virtually zero. You can also have a meta 'program object' class that wraps everything up with inheritance or has-a tricks to open access paths (old MFC / dialog programs did this design as an example). I don't know if that design pattern is still acceptable or not in modern theory, but it works and a lot of old windows gui projects used it.

you can also do various nefarious things to have a 'cleaner' global... you can put it into a class as a static member and then in any function, declare a variable of the class to get access to it. You can put them in a namespace to offer at least a little protection against name collision, but that only helps a little (solves 2 or 3 of a dozen problems). There are similar tricks you can play but most of this is, at best, weird and at worst bad code.

Topic archived. No new replies allowed.