Why Does Reading a Record Struct Fields from Std::Istream Fail, and How to Fix It

constructor does not work correctly while reading variables from txt file

The problem results from the wrong used algorithm and wrongly placed statements.

So, let's look what is going on in the below:

long linecount;
for(linecount=0;getline(pbin,line);linecount++)
;
contact* myArray = new contact[linecount];
pbin.seekg(0);
if(pbin.is_open()){
int i;
for(i=0;i<linecount;i++){
if(pbin!=NULL) {
while(pbin>>myArray[i]);
}
}
pbin.close();

You want to count the lines. So you read all lines until the eofstate is set. But, additionally, also the fail bit will be set. See also here.

If you use your debugger, you will find a 3 in _Mystate.

After all std::getline

Then you perform a seekg. This will reset the eof bit but keep the fail bit. The dubugger shows then

After seekg

You can see that the fail bit is still set.

So, and this will now lead to the main problem. If your write if(pbin!=NULL) which is definitely wrong (on my machine is does not even compile), or if you better write if(pbin) the fail bit will still be set. And because the bool and the ! operator for streams is overwritten (please see here) the result of the if and while will be false and your pbin>>myArray[i] will never be executed.

So, a pbin.clear() would help.

But, although your class definition is already very good, with inserter and extractor overwritten, you do not use the full C++ power for reading the data.

One basic recommendation would be to never use raw pointers for owned memory. And best also not new. Use dedicated containers for your purpose. E.g. a std::vector. The you can use the std::vectors constructor no 5 together with a std::istream_iterator. Please read here. The range based constructor for the std::vector will copy data from a given range, denoted by the begin and end iterator. And if you use the std::istream_iterator, it will call your overwritten extractor operator, until all data are read.

So your main shrinks to:

int main() {

// Open source file and check, if it could be opened
if (ifstream pbin("r:\\phoneData2.txt");pbin) {

// Read complete source file
std::vector data(std::istream_iterator<contact>(pbin), {});

// Show data on console
std::copy(data.begin(), data.end(), std::ostream_iterator<contact>(std::cout, "\n"));
}
return 0;
}

This looks by far compacter and is easier to read. We start with an if-statement with initializer. The initializer parts defines the variable and the constructor will open the file for us. In the condition part, we simple write pbin. And, as explained above, its bool operator will be called, to check if everything was ok.

Please note:

  1. We do not need a close statement, because the destructor of the
    std::ifstream will close the file for us.
  2. The outer namespace will not be polluted with the variable name pbin. That is one of the reasons, why ifstatement with initializer should be used.

We alread descibed the std::vector with its range constructor. SO reading the complete file is simple done by the very simple statement

std::vector data(std::istream_iterator<contact>(pbin), {});

Please note:

  1. We do not define the type of the std::vector. This will be automatically deduced by the compiler through CTAD
  2. We use the default initialzer {} for the end iterator, as can be seen here in constructor number 1.

The whole program could then be rewritten to:

#include <iostream>
#include <string>
#include <fstream>
#include <vector>
#include <algorithm>
#include <iterator>

using namespace std;

class contact {
private:

int listno;
string name;
string surname;
string phonenumber;

public:
contact() {
this->name = "Unknown";
this->surname = "Unknown";
this->phonenumber = "Unknown";
this->listno = 0;
}
contact(string name, string surname, string phonenumber) {
this->name = name;
this->surname = surname;
this->phonenumber = phonenumber;
}
contact(int listno, string name, string surname, string phonenumber) {

this->name = name;
this->surname = surname;
this->listno = listno;
this->phonenumber = phonenumber;
}

friend ostream& operator<< (ostream& out, const contact& con) {
out << con.listno << '\t' << con.name << '\t' << con.surname << '\t' << con.phonenumber;
return out;
}

friend istream& operator>> (istream& in, contact& con) {
in >> con.listno >> con.name >> con.surname >> con.phonenumber;
return in;
}
};

int main() {

// Open source file and check, if it could be opened
if (ifstream pbin("r:\\phoneData2.txt");pbin) {

// Read complete source file
std::vector data(std::istream_iterator<contact>(pbin), {});

// Show data on console
std::copy(data.begin(), data.end(), std::ostream_iterator<contact>(std::cout, "\n"));
}
return 0;
}

How to read specific amount of character from file to struct

You're almost right. istream::getline() waits for char * at the first argument but you're are passing std::string. Keep in mind if getline doesn't read symbols to delimiter (by default, end of line and it is your case) it sets failbit for input stream (duom). You need to istream::clear() it. Also you're mixing duom >> n; and file reading. Maybe you want to read info for structure from file and n from cin?

UPDATE

You need to skip a new line character with istream::ignore(). Also your example has name with 20 symbols at 8 line but istream::getline extracts one less than the specified. Following code doesn't contain any error checking.

int main()
{
ifstream duom("U2.txt");
competition athletes[30];
int n; // amount of athletes
duom >> n;
duom.ignore(1);

char name[256];
for (int i = 0; i < n; i++) {
duom.getline(name, 21);
if (duom.fail())
duom.clear();
athletes[i].name = name;
duom >> athletes[i].athleteNum >>
athletes[i].startHours >>
athletes[i].startMin >>
athletes[i].startSec;
duom.ignore(1);
}
return 0;
}

If you don't want to read other athlete members you should also skip them with istream::ignore() instead of reading with duom >> ...:

duom.ignore(32); // skip up to 32 characters or '\n' 

https://en.cppreference.com/w/cpp/io/basic_istream/ignore

How to read in from a file word by word and assign those words to a struct? [duplicate]

In this piece of code

while (file >> file_string) {
b[count].title = file_string;
b[count].author = file_string;
count++;
}

you read one word and assign the same value to title and author, don't expect the compiler to guess your intentions ;)

Some additional hints and ideas:

while(!file.eof() is not what you want, instead put the input operations into the loop condition. And you can skip the intermediate string and read directly into title/author:

void getBookData(book* b, int n, ifstream& file) {
int count = 0;
while((file >> b[count].title >> b[count].author) && count != n-1) {
count++;
}
}

Trouble reading in data from a file using data structures

As a general preliminary remark, I think that even for learning purpose, this kind of exercise should better let you use std::strings instead of c-strings and std::vector for keeping a growing number of items.

What's wrong in your code ?

The first problem is that you use the same counter count to populate your agency array AND the car array. This will cause you very quickly to have a counter beyond the array boundaries and corrupt memory.

Solution: rework your loop structure using 2 distinct counters.

Next problem is that you don't identify the end of the car list of an agency. This makes it unrealistic to read more than one agency: you'll experience a failure on the stream reading that will prevent you getting anything usefull in your data.

Solution: analyze failures on reading to identify going from cars ( first element should be a number) to a new agency ( first element is a string).

In addition, you might have some strings which are longer than allowed by your character arrays, causing further memory corruption.

Solution: limit the number of chars read using iomanip() to fix maximum width. This is strongly recommended unless you go for std::string

Last issue: the variable length arrays are not a standard C++ feature, even if some popular compilers support it.

Solution: Either use dynamic allocation with new/delete or opt for the purpose of this excercise to use a constant maximum size.

Code snippet:

Adapted, without choices, menus, etc. , the reading would look like:

const int carAmount = 30;    // !!!
const int agencyAmount = 10; // !!!
agency agencyLib[carAmount];
car carLib[carAmount];
ifstream carInData ("test.dat");
int numCar = 0, numAgency = 0; // !!! shows the real number of items available
int count1, count2; //

cout << "Start reading" << endl;
for (numAgency = numCar = 0; carInData && numAgency < agencyAmount; numAgency++) {
if (!(carInData >> setw(sizeof(agencyLib[numAgency].company)) >> agencyLib[numAgency].company >> agencyLib[numAgency].zip))
break; // if nothing left, exit loop immediately
for (; numCar < carAmount; numCar++) {
carInData >> carLib[numCar].year >> setw(sizeof(carLib[numCar].make )) >>carLib[numCar].make
>> setw(sizeof(carLib[numCar].model))>>carLib[numCar].model
>> carLib[numCar].price >> carLib[numCar].available;
if (carInData.fail()) { // here we expect a year, but get an agency string
carInData.clear();
break;
}
strcpy(carLib[numCar].agency, agencyLib[numAgency].company);
carLib[numCar].zip = agencyLib[numAgency].zip;
}
}

And the subsequent display:

cout << "Display agencies: " << endl; 
for (count1 = 0; count1 < numAgency; count1++) {
cout << agencyLib[count1].company << " " << agencyLib[count1].zip << "\n";
}
cout << "Cars: " << endl;
for (count2 = 0; count2 < numCar; count2++) {
cout << carLib[count2].agency << " " << carLib[count2].zip << ": ";
cout << carLib[count2].year << " " << carLib[count2].make << " " << carLib[count2].model << " " << carLib[count2].price << " " << "\n";
}

Note that there's no link beteween agencies and cars (except the common fields), so the display just shows two distinct lists.

Improving error processing for an istream helper class when using exceptions

I was so concentrated on searching std functions to solve that, that I didn't think of the most obvious solution: hijacking the std exception thrown. In case it could help s.o. else:

I first defined a dedicated nested failure class:

class mandatory_input { 
public:
...
class failure : public std::istream::failure {
public:
failure(std::error_code e);
};
};

Then I have added the following bloc in the original error processing code (see question):

    // start of enhanced exception handling     
if (is.exceptions() & std::istream::failbit) { // if exception will be thrown
try {
is.setstate(std::ios::failbit);
} catch (std::istream::failure &e) { // the failbit will trigger this
throw mandatory_input::failure(e.code()); // and i throw my own
} catch (std::exception &e) { // just in case other low-level errors would be thrown
throw e;
}
} else //======= end of enhanced exceptions handling

Now with this solution, the clients of my helper class who want to use .exceptions() can process errors either undifferentiated:

try { cin >> mandatory_input(" ( + ") >> country >> .... ;  
} catch (istream::failure e) {
cerr << "Input error: "<< e.code()<< " " << e.what();
}

or fine tune error processing:

try {  ....   
} catch (mandatory_input::failure e) {
cerr << "Input format mismatch: " << mandatory_input::getexpected()
<< " was expected, but " << mandatory_input::getread_error() << " was read !\n";
} catch (istream::failure e) {
cerr << "Input error: "<< e.code()<< " " << e.what();
}

How to iterate over a line in a file? [duplicate]

You're almost there; you can use formatted stream extraction to read integers, using a string stream to represent each line:

#include <fstream>
#include <sstream>
#include <string>

// ...

for (std::string line; std::getline(infile, line); )
{
std::istringstream iss(line);
for (int n; iss >> n; )
{
std::cout << "Have number: " << n << "\n";
}
std::cout << "End of line\n";
}

Error checking can be added by checking whether the entire string stream has been consumed.

Incorrect char array length when using reinterpret_cast to store data with struct object [duplicate]

As your char array is not nul terminated, you have to display each character instead of the (decayed) const char*:

template <std::size_t N>
void print(const char (&a)[N])
{
for (auto c : a)
std::cout << c;
}

and then

Student student;
// init student
// ...


print(student.f_name); // instead of std::cout << student.f_name;
print(student.l_name); // instead of std::cout << student.l_name;
std::cout << student.camp_code;
// ...


Related Topics



Leave a reply



Submit