Use one fstream object to write and read on two files

closed account (zb9zb7Xj)
Hello everyone, I'm trying to do the following using fstream class
- read from the first file
- write or read on a second file based on the user's input

I won't write all my code but just the parts that I think are necessary to understand my question.

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
  fstream fileStream;
  fileStream.open("./files/articles.txt", ios_base::in);

  while cycle 
    read from the file and add the read line to a vector
  fileStream.close();

  fileStream.open("./files/cart.txt"); //if I understood correctly it's by default: ios_base::in | ios_base::out
  while cycle
    if the user types a valid article code
      fileStream << all necessary info taken from the vector; //let's call this line L1
    if the user types "cart"
      cout << printCart(fileStream);
      /*NOTE: I've noticed that the file is empty until it's closed outside this while cycle
      should I close it after L1? If yes, can I open it using ios_base::app?*/
    if the user types "exit"
      break;
  fileStream.close();

//printCart function

string printCart(fstream & fs) {
//I don't know what to write in here to get the information from the file "cart.txt" to a stringstream; my attempt
  string s;
  ostringstream ss;
  while (!fs.eof()) {
    fs.getline(&s[0],1000);
    ss << s;
    s.clear();
  }
  return ss.str();
}


I've also tried to use ifstream for the first thing, ofstream for the second thing, but then I don't know how to read from ofstream object and put the read stuff into a stringstream... Anyway, I'd still prefer to just use one fstream object instead of 1ifstream + 1ofstream.
Last edited on
I recommend using ifstream and ofstream unless you really need to read and write to the same stream at the same time.

/*NOTE: I've noticed that the file is empty until it's closed outside this while cycle
should I close it after L1? If yes, can I open it using ios_base::app?*/

You can also "flush" the stream buffer to insure the data is written to the file.

//I don't know what to write in here to get the information from the file "cart.txt" to a stringstream

What don't you understand? You write to a stringstream just like you would write to any other type of stream.
closed account (zb9zb7Xj)
First of all, thanks for the flush tip, I didn't know that function. I've tried it: it just updates the cart.txt the first time it's called (aka the first time that user types "cart"), whereas the close/open does work every time.
NOTE: it works fine with the IFstream + OFstream solution

What I don't understand is:
1) why my attempt doesn't work (cout << printCart returns nothing)

I'm getting close to understand why this happens: first of all, since I've a problem with the flush I've used file.Close() immediately before calling printCart(): I reopen the file as first instruction in printCart(). But still, the fstream.getline function doesn't produce any result. What am I doing wrong?


2) why can't I pass the fstream (set to in) to the std:getline function (http://www.cplusplus.com/reference/string/string/getline/)? Isn't it the same as passing it an ifstream object?

3) if I use two objects, one ifstream and one ofstream, how can I implement the printCart()?

I figured out the answer for 3): before calling the printCart(ifstream) I make a flush of outFile (cart.txt), open an inFile passing it my cart.txt file and call the printCart passing this inFilt to it: this way I can use the std::getline. I've still got a question on this: is it better to close the file in the printCart(ifstream) or after it, in the main? Why?

Last edited on
1) why my attempt doesn't work (cout << printCart returns nothing)

Look at this snippet:
1
2
3
4
  string s;
  ostringstream ss;
 while (!fs.eof()) {
    fs.getline(&s[0],1000);

Why are you trying to get a C-string into a std::string? It would probably be better if you used the proper getline() function for a std::string: getline(fs, s);


2) why I can't use std:getline function (http://www.cplusplus.com/reference/string/string/getline/) to pass a fstream set to in (isn't it the same as passing to it an ifstream object?)

What? But no a std::string is not a stream.

3) if I use two objects, one ifstream and one ofstream, how can I implement the printCart()?

I really can't tell what you're trying to do in the printCart() function, you need to provide more content. I recommend a small complete program that illustrates your problem and also provide a small sample of your input file.


I'd also like to make it work with the single fstream but I can't fix the problem.

Do you realize that an fstream works with a single file, allowing interleaved reading and writing on that one file. Also when switching between writing and reading you need to flush the output stream before you try to read from the stream.

Why are you even trying to use a stringstream, why not just get the data from the file into the proper variables?

closed account (zb9zb7Xj)
I really can't tell what you're trying to do in the printCart() function, you need to provide more content.


I'll to be more specific, but English is not my mother tongue so it may be hard to understand it. I'm writing a simple program in various languages and variations for each language. In C++ I want to: use 1 filestream or 1 ifstream and 1ofstream, use the std:getline and the istream::getline, use strings, c-strings and stringstreams.
The program takes data from art.txt formatted as a list of: "code|name|price", uses it to fill a string vector (this works fine), then uses this vector to fill a ART vector (with ART being a class with string code, name and float price, this works too), then ask the user which articles he wanna buy and, based on code given, adds the article (formatted as "code - name - price euro" to a cart.txt file (this works, but only if I 1ifstream and 1ofstream)). I want to achieve this by using a single fstream:
* first use it in input mode to read from art.txt
* then close it and
** use it, in output mode to write on cart.txt (when user types a valid code)
** use it, in input mode to read from cart.txt (when user types "cart")

At this point, the problem is that cart.txt is not updated until the while cycle in which the user is asked to either type a code or "cart" has ended
*first way: I close fs (this updates cart.txt), call the printCart(fs) and in the function open fs(cart.txt) in input mode, use the ifstream.getline to store its content in a stringstream (I need to fix this and I think I know how thanks to your first quote), then fs is closed again and it's reopened (cart.txt) after the function has ended (so in the while cycle) in append mode. Do I need to flush the file, or having closed it before the function will suffice?
*second way: I DON'T close fs but flush it (I may have understood wrongly, but doesn't this mean that the cart.txt file is updated with the current buffer in fs (which is in output mode)?), call the printCart(fs) that reads from it: now that I write it down I don't think this should be possible: I have no way of reading from, fs, since it's still opened in output mode. Is this correct?

Is this what you were saying here?
Also when switching between writing and reading you need to flush the output stream before you try to read from the stream.


For both of these ways I'd like to use the string::getline and the istream::getline in the printCart() (and I'm working on it right now)

Why are you trying to get a C-string into a std::string? It would probably be better if you used the proper getline() function for a std::string: getline(fs, s);


I didn't know char* was a c-string: isn't char* and &string[0] the same? I mean, aren't them both an address of memory of a single character? I'll try to declare a char array and use it, again, just for learning how to do something.
UPDATE: I don't understand why in the example at http://www.cplusplus.com/reference/istream/istream/getline/ name and title are passed instead that name[0]...

What? But no a std::string is not a stream.

No I mean, the first parameter is an istream &, can I pass it a fstream& (so an iostream) in input mode instead?


Thank you for your time in helping me, I really appreciate it.
Last edited on
In C++ I want to: use 1 filestream or 1 ifstream and 1ofstream, use the std:getline and the istream::getline, use strings, c-strings and stringstreams.

I'd recommend you start with std::string, leaving C-strings until much later, perhaps until you try to cover C.

The program takes data from art.txt formatted as a list of: "code|name|price"

It would be helpful if you posted a small sample of your input file. Also it would be helpful if you would show the code where you read the datafile. Do you know that you can read the datafile directly into your ART class, really a vector of your ART class. It would also be helpful to see the definition of this class.

then uses this vector to fill a ART vector (with ART being a class with string code, name and float price, this works too),

This should be where the stringstreams come into play.

I want to achieve this by using a single fstream:

Why? You have two different files, a fstream is really meant for a single file.

I didn't know char* was a c-string: isn't char* and &string[0] the same? I mean, aren't them both an address of memory of a single character?

Well what a char* points to can be one of two/three things depending on where and how it is actually defined.
1. a pointer to a single character.
2. a pointer to an array of char.
3. a pointer to a array of char terminated by the end of string character ('\0').

I'll try to declare a char array and use it, again, just for learning how to do something.

Again I would recommend leaving C-strings and arrays till later. First start by learning to use C++ constructs like std::vector, std::string, etc.

isn't char* and &string[0] the same?

No, a std::string is a C++ standard class. Trying to use a pointer to the string may or may not work, especially if the std::string happens to be empty. You should avoid trying to manipulate a std::strings data using anything other that the methods of the std::string class.

No I mean, the first parameter is an istream &, can I pass it a fstream& (so an iostream) in input mode instead?

Yes you are able to pass a fstream into a function that has been declared to take a ifstream. However you can only use that fstream as an input stream in that function because an istream is only an input stream. Also don't forget that an istream (any istream) will not create a file if it doesn't exist by default, and by default a ostream will "erase" the file contents. If you want some other behavior you need to be sure to add some additional flags when opening the files.

Also note that cin has istream as it's base, cout has ostream as it's base. There is really no class called iostream, the iostream header just defines several standard streams like cin and cout.

Lastly for now, I would recommend that you first get the program working using std::ifstreams, std::ofstreams, and std::stringstreams before you try to tackle using fstreams. The fstream interface, IMO, is a little harder to understand and has a few tricky requirements.

closed account (zb9zb7Xj)
Thank you for the detailed answer, here's the full code: I've translated it since it was in another language, I hope I copy/pasted everything correctly. I've added some comments with questions etc.

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
//ARTICLE.h

#include <string>
#include <sstream>

class ARTICLE {
  private:
    std::string code;
    std::string name;
    float price;

  public:
    ARTICLE();
    ARTICLE(std::string, std::string, float);

    std::string getCode();
    std::string getName();
    float getPrice();

    std::string printArt ();
};

//ARTICLE.cpp

#include "ARTICLE.h"
using namespace std;

ARTICLE::ARTICLE() {
  code = name = "";
  price = 0;
}

ARTICLE::ARTICLE(string cod, string nam, float pr) {
  code = cod;
  name = nam;
  price = pr;
}

string ARTICLE::getCode() { return code; }
string ARTICLE::getName() { return name; }
float ARTICLE::getPrice() { return price; }

string ARTICLE::printArt () {
  ostringstream ss;
  ss << code << " - " << name << ", " << price << " euro\n";
  return ss.str();
}


//FUNCTIONS.h

#include <vector>
#include <fstream>
#include "ARTICLE.h"

std::string printAll(std::vector <ARTICLE> a);
std::string printCart(std::ifstream & ifs);
std::string tolowerS (std::string s);
bool isValid (std::string s, std::vector<ARTICLE> a, int & curr);

//FUNCTIONS.cpp

#include <iostream>
#include "FUNCTIONS.h"
using namespace std;

string printAll(vector <ARTICLE> a) {
  ostringstream ss;
  for (int i = 0; i<a.size(); i++)
    ss << a[i].printArt();
  return ss.str();
}

string printCart(ifstream & ifs) {
  string s;
  ostringstream ss;
  while (!ifs.eof()) {
    getline(ifs, s);
    ss << s << "\n";
  }
  ifs.close(); //is it okay to close the file here or should I close it outside the function call in the main?
  return ss.str();
}

string tolowerS (string s) {
  for (int i=0; i<s.size(); i++)
    s[i]=char(tolower(s[i]));
  return s;
}

bool isValid (string s, vector<ARTICLE> a, int & curr) {
  for (curr=0; curr<a.size();curr++) {
    if (a[curr].getCode() == s)
      return true;
  }
  return false;
}



//MAIN.cpp

//if I include <iostream> and other headers here, will the preprocessor write the library code twice? If yes, how can I prevent this?
#include "FUNCTIONS.h"
using namespace std;

#define N 1000

int main() {
  vector <ARTICLE> Storehouse;
  vector <string> Attributes;

  float totalPrice=0, tkp=0;
  int posDel=0, qty=0, curr=0;
  const char delim='|';
  string row="", tkc="", tkd="", code="";


  ifstream fileIn;
  fileIn.open("./files/articles.txt"); //I know you can initialize this directly, but I prefer to keep it separate: any good reasons not to?
  /*
  articles.txt:
  A1|Article1|60.13
  A2|Article2|10.49
  A3|Article3|49.99
  A4|Article4|25.87
  A5|Article5|4.20
  */


  while (!fileIn.eof()) {
      getline(fileIn,row);
      row += "\n";
      Attributes.push_back(row);
    }
  fileIn.close();
  Attributes.pop_back(); //last element is '\n', so I remove it. I think this happens because of !eof() but I don't know how to prevent it...

  //I'd like to keep this for cycle separated from the previous while cycle
  for (int i=0; i<Attributes.size(); i++) {
    row = Attributes[i];

    posDel = row.find(delim);
    tkc = row.substr(0,posDel);
    row.erase(0, posDel+1);

    posDel = row.find(delim);
    tkd = row.substr(0,posDel);
    row.erase(0, posDel+1);

    posDel = row.find(delim);
    tkp = stof(row);

    Storehouse.push_back(ARTICLE(tkc,tkd,tkp));
  }

  ofstream fileOut;
  fileOut.open("./files/shopping.txt");
  while (true) {
    cout << "Insert the code of the article you wanna buy, ex to exit, info for a list of available articles and cart for you cart: ";
    cin >> code;
    if (code == "ex")
      break;
    else if (tolowerS(code)=="info")
        cout << printAll(Storehouse);
    else if (tolowerS(code)=="cart")
      if (totalPrice==0)
        cout << "You haven't bought anything yet\n";
      else {
        //Variation: fileOut.close()
        fileOut.flush();
        fileIn.open("./files/shopping.txt")
        cout << "Your chart:\n" << printCart(fileIn);
        //Variation: fileOut.open("./files/shopping.txt", ios_base::app)
      }
    else if (isValid(code,Storehouse,curr)) {
      while (true) {
        cout << "How many articles do you wanna buy? ";
        cin >> qty;
        if (qty <= 0)
          cout << "Provide a number > 0";
        else if (cin.fail()) {
          cout << "Provide a numeric value!\n";
          cin.clear();
          cin.ignore(N,'\n');
          //despite my efforts, if the user types a valid code and then, when prompted for a quantity
          //he provides a non-numeric value the program goes into an unlimited loop
        }
        else {
          totalPrice += qty * Storehouse[curr].getPrezzo();
          fileOut << "Cod. " << Storehouse[curr].getCode() << " - " << Storehouse[curr].getName() 
                     << " " << Storehouse[curr].getPrice() << " €, quantity: " << qty << "\n";
          if (qty==1) {
            cout << "1 article added to the cart\n";
            break;
          }
          cout << qty << " articles added to the cart\n";
          break;
        }
      }
    }
    else
      cout << "Invalid code\n";
  }
  fileOut.close();


  if (totalPrice==0) {
    cout << "Wow you're stingy";
    return 0;
  }
  cout << "Total: " << totalPrice << " euro";
  return 0;
}
Last edited on
//is it okay to close the file here or should I close it outside the function call in the main?

It would be better to let the stream close itself when it goes out of scope. If you "must" close the file I recommend closing it in the same function that opened it. You might even want to consider passing a file name to this function so that it can completely handle the file duties. By the way you really should be checking that the files open correctly before you try to use them.

if I include <iostream> and other headers here, will the preprocessor write the library code twice? If yes, how can I prevent this?

No, standard headers are designed to prevent such problems by using header guards. Headers you design should also be using header guards to prevent multiple inclusions.
By the way it is considered a poor practice to have required headers #included by some other file.

//I know you can initialize this directly, but I prefer to keep it separate: any good reasons not to?

Other than it is considered a best practice in C++ to strive to use RAII practices. Meaning you should prefer using constructors to properly construct the complete object and then let the destructors handle the cleaning up.

//last element is '\n', so I remove it. I think this happens because of !eof() but I don't know how to prevent it...

The best way to prevent this issue is to not use eof() to control your read loop, use the actual read itsel instead.

1
2
3
4
  while (getline(fileIn,row)) {
     //      row += "\n"; // Why are you doing this? It should not be needed, nor even wanted.
      Attributes.push_back(row);
    }



//I'd like to keep this for cycle separated from the previous while cycle

Okay, but do you realize that stringstreams would probably greatly simplify this loop?

//Variation: fileOut.close()

Variation 2: Rework your program to keep the "Cart" information in memory (in a vector) and only print it to the file when the user is finished adding things to the file.


//despite my efforts, if the user types a valid code and then, when prompted for a quantity
//he provides a non-numeric value the program goes into an unlimited loop

Yea, users are like that always entering invalid data at the most inopportune times. Validating user entry is probably some of the hardest coding problems.

You should start thinking about simplifying things, using more small single purpose functions would probably be quite helpful.
closed account (zb9zb7Xj)
I got it! I'll work on it some more days and eventually I'll open a new topic if I encounter any new problem.

About this question
Okay, but do you realize that stringstreams would probably greatly simplify this loop?


Yes, now I do. I'll also try a variation using them!

By the way you really should be checking that the files open correctly before you try to use them.


Do I do that by using the is_open()?


Thank you again, I've understood a lot thanks to your answers :)
Last edited on
Do I do that by using the is_open()?

You can, but remember that only checks one of the possible stream error flags. I normally just use something like: if(stream) where stream is the name of the stream you're using to open the file.

I got it!

It would be nice if you posted the "final" code so that perhaps others could learn from your work.

closed account (zb9zb7Xj)
Okay, sure I'll edit the first message and add the final code between spoilers in the next days :)
Okay, sure I'll edit the first message

Please DON'T do that. It would make this thread look nonsensical to anyone trying to read it.
closed account (zb9zb7Xj)
I see, even if I just APPEND text without deleting the previous one? I usually do that, when a problem is solved I edit the first topic and add, at the end of it, a "SOLUTION" between spoiler tags. If it's considered a bad practice I could just add a comment, but I feel that this will bump the thread on top even if the problem is solved...
It's okay to bump a topic, this isn't exactly a fast forum.
We don't have spoiler tags sadly so that won't work.
(I'd say feel free to edit the first post, but only to add to it in a clearly marked "solution" area, not to remove existing content. Hope that makes sense.)
Topic archived. No new replies allowed.