0

I am doing a project where I am writing automated billing system for a fake restaurant. The program is supposed to take a text file containing the menu, put it into a array or vector of structs, show the menu, let the customer order, and print a receipt. I am using a global vector of structs for the menu.

This block of code is everything related to the problem.

`

#include <iostream>
#include <fstream>
#include <vector>
//there is more code to this program, but the fault occurs very soon in the program
//and none of the rest of the code has any relevance.
//also, I don't really think that the problem is with trying to input, but I don't have enough experience to rule it out.
using namespace std;

struct menuItemType
{
  string menuItem; //this is the name of the item
  double menuPrice; // this is the price of the item
  int menuCount;
};

vector<menuItemType> menuList; //the menu can be any size so I don't know how big it will be at this point. I'm using a vector to avoid having to declare a size
// I also have 2 other functions and some extra code in main that all need to access this vector. That is why I made it global

void getData() //this function opens the text file containing the menu and tries to read in each line.
{
    ifstream input;
    input.open("inData.txt");

    input.peek();
    int i = 0;
    string item;
    double price;

    while(!input.eof())
    {
        getline(input,menuList[i].menuItem); //This is the line creating the fault.
        input >> menuList[i].menuPrice;

        i++;
        input.peek();
    }
}
int main()
{
    getData();
    return 0;
}

`

I have tried debugging and have determined that the segmentation fault is not specific to the line commented in the code snippet. The fault seems to occur whenever I try to input to a member of a struct inside the vector. I've tried using cin as well so I don't believe the text file stream is the problem. The text file looks like this:

Bacon and eggs 1.00 Muffin 0.50 Coffee 0.90

Specifically, my question is: Why does trying to input to a member of a struct inside the vector cause a segmentation error and how can I fix it.

Sorry for the long explanation and awkward formatting. I am fairly new to both stack overflow, and c++.

Gavin
  • 23
  • 3
  • Welcome to SO :) – Nox Nov 09 '18 at 14:45
  • 1
    Worth to look at: [Why is iostream::eof inside a loop condition considered wrong?](https://stackoverflow.com/questions/5605125/why-is-iostreameof-inside-a-loop-condition-considered-wrong) – Rhathin Nov 09 '18 at 14:48
  • Thanks Nox, so far SO is probably the most helpful site I've ever seen in relation to programming. Also to Rhathin, I read the question in the link, and I think I understand that using `.peek()` and `.eof` in a loop is generally bad code etiquette, but it's a little over my head as to why and how to fix it. – Gavin Nov 09 '18 at 14:58
  • @Gavin Take a look at my example, the design pattern will help you with handling files. This kind of approach makes it easier to work with. Store the contents into a string or strings, stream or buffer, then after you have all the data you need in those variables - containers, close your file handle as you are done with it. Then it's just a matter of parsing those strings or buffers... The parsing part is determining what kind of data the string represents then converting it to that type and storing it into your struct or class, then after that save the struct to vector. – Francis Cugler Nov 09 '18 at 15:47

3 Answers3

1

When retrieving data from a file; I tend to prefer to retrieve the contents of a single line and store that to some string, stream or buffer and parse it later, or I'll retrieve the entire contents of a file and do the same. I find it easier to parse a string after you have extracted the data from the file and closed its handle. I do not like using global variables that are NOT CONST. Also the way you are using your for loop when reading from a file while( file.eof() ) or while ( !file.eof() ) is bad practice and can lead to many errors, crashes and headaches later on. If you look at my function below all it does is it takes a filename and tries to open it if it exists. Once it opens, then it will get a line save it to a string and push that string into a vector until there is nothing else to read. Then it closes the file handle and returns. This fits the concept of a function having a single responsibility.

If you have a function where you open a file, read in a line, parse the data, read a line, parse the data etc. then close it; this kind of function is considered to take on multiple tasks which can be a bad thing. First there are performance reasons. Opening and reading from a file itself is a computationally expensive task so to speak. You are also trying to create objects on the fly and this can be bad if you never checked to validate the values you received from the file. Take a look at my code below and you will see the design pattern that I'm referring to where each function has its own responsibility. This also helps to prevent file corruption.

#include <vector>
#include <string>
#include <sstream>
#include <iostream>
#include <fstream>
#include <exception>

struct MenuItem {
  string menuItem; 
  double menuPrice; 
  int menuCount;
};

// This function is not used in this case but is a very helpful function
// for splitting a string into a vector of strings based on a common delimiter
// This is handy when parsing CSV files {Comma Separated Values}.
std::vector<std::string> splitString( const std::string& s, char delimiter ) {
    std::vector<std::string> tokens;
    std::string token;
    std::istringstream tokenStream( s );
    while( std::getline( tokenStream, token, delimiter ) ) {
        tokens.push_back( token );
    }

    return tokens;
}

void getDataFromFile( const char* filename, std::vector<std::string>& output ) {
    std::ifstream file( filename );
    if( !file ) {
        std::stringstream stream;
        stream << "failed to open file " << filename << '\n';
        throw std::runtime_error( stream.str() );
    }

    std::string line;
    while( std::getline( file, line ) ) {
        if ( line.size() > 0 ) 
            output.push_back( line );
    }
    file.close();
}

void parseFileData( const std::vector<std::string>& fileContents, std::vector<MenuItem> menuItems ) {
    // The first param is the contents of the file where each line
    // from the file is stored as a string and pushed into a vector.

    // Here you need to parse this data. The second parameter is the
    // vector of menu items that is being passed by reference.

    // You can not modify the fileContents directly as it is const and read only
    // however the menuItems is passed by reference only so you can update that

    // This is where you will need to go through some kind of loop and get string
    // of text that will stored in your MenuItem::menuItem variable.
    // then the next string will have your price. Here you showed that your
    // text file has `$` in front of the value. You will then have to strip this out 
    // leaving you with just the value itself. 
    // Then you can use `std::stod( stringValue ) to convert to value, 
    // then you can save that to MenuTiem::menuPrice variable.

    // After you have the values you need then you can push back this temp MenuItem
    // Into the vector of MenuItems that was passed in. This is one iteration of
    // your loop. You continue this until you are done traversing through the fileContents vector.


    // This function I'll leave for you to try and write.        
}

int main() {
    try {
        std::vector<std::string> fileConents;
        getDataFromFile( "test.txt", fileConents );
        std::vector<MenuItem> data; // here is the menu list from your example
        generateVectors( fileConents, data );

        // test to see if info is correct
        for( auto& d : data ) {
            std::cout << data.menuItem << " " << data.menuPrice << '\n';
        }

    } catch( const std::runtime_error& e ) {
        std::cerr << e.what() << '\n';
        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
}

As for your error or crash, you were probably either accessing an index that is past the end of the vector, or you were trying to use contents from the vector that had invalid data.

Francis Cugler
  • 7,462
  • 1
  • 24
  • 44
  • Okay, that makes sense that it would use a lot more resources. As for my program, it runs now, but when it tries to get the menu items and their prices from the text file, it returns the first item and its price, but it returns blanks after that even though there are still 2 more items. Is what you mentioned above possibly the reason for that? Also, the $ was a mistake. My file doesn't actually have that. – Gavin Nov 09 '18 at 16:12
  • @Gavin use your debugger and check the strings that are being pushed into the vector one line at a time. You might have a space, a null character, end line, or carriage return that you will have to handle. If the vector of strings that are read from the file looks correct right before the function returns to the caller, then the issue would probably be in your function that is parsing the data. – Francis Cugler Nov 09 '18 at 16:14
  • I haven't made a parsing function yet. I did amend the loop so I don't have `.eof` anymore. Im using `getline` to check for more lines. I ran into a curious problem. First, when I debugged initially, after the first item and price was returned `getline` returned "" rather than a endline or space. Second, for some reason, I can't debug anymore because I get a segmentation fault, but if I run normally, I don't get the error. – Gavin Nov 09 '18 at 16:29
  • The debug says `Program terminated with signal SIGSEGV, Segmentation fault. The program no longer exists.` – Gavin Nov 09 '18 at 16:31
  • @Gavin for that I'm not sure, I don't know what compiler you are using nor what OS you are running on. – Francis Cugler Nov 09 '18 at 17:01
0

If you look at the operator[] of a vector and then check on the exception section, it will tell you that if n is superior than the size of the vector, it's actually undefined behavior. You probably want to push back an item you've created beforehand.

I usually prefer vector::at as it is bound-checked and signals if the requested position is out of range by throwing an out_of_range exception.

Nox
  • 832
  • 1
  • 8
  • 24
  • Indeed, using `menuList.push_back` will solve the problem. – Matthieu Brucher Nov 09 '18 at 14:49
  • So does that mean it would be better to create a temporary struct variable and then use `push_back` to add it to the vector, rather than attempt to read data directly into the vector? – Gavin Nov 09 '18 at 14:54
  • if by better you mean working, then yes! :) if you find that an answer did actually helped and answered your question you can mark it as an answer :), it will also give me a few points. – Nox Nov 09 '18 at 14:59
  • Thank for the help. I tried doing that and it stopped giving me a segmentation fault. Instead, its now giving me a "bad_alloc" error. I debugged and found out that the `.eof` and `.peek()` loop isn't working quite so well and now I have an infinite loop. That's probably why Rhathin mentioned it. – Gavin Nov 09 '18 at 15:08
0

First remove "$" from inData.txt then I suggest to use while(getline(input, item)) like this way:

#include <iostream>
#include <fstream>
#include <vector>
#include <math.h>
//there is more code to this program, but the fault occurs very soon in the program
//and none of the rest of the code has any relevance.
//also, I don't really think that the problem is with trying to input, but I don't have enough experience to rule it out.
using namespace std;

struct menuItemType
{
  string menuItem; //this is the name of the item
  double menuPrice; // this is the price of the item
  int menuCount;
};

vector<menuItemType*> menuList; //the menu can be any size so I don't know how big it will be at this point. I'm using a vector to avoid having to declare a size
// I also have 2 other functions and some extra code in main that all need to access this vector. That is why I made it global

void getData() //this function opens the text file containing the menu and tries to read in each line.
{
    ifstream input;
    input.open("inData.txt");

    int i = 0;
    string item;
    double price;

    while(getline(input, item))
    {
        menuList.push_back(new menuItemType);
        menuList[i]->menuItem = item;
        getline(input,item);
        menuList[i]->menuPrice = atof((char*)item.c_str()); //math.h
        i++;
    }
}
int main()
{
    getData();
    for(menu : menuList)
    {
        cout << menu->menuItem << ": " << menu->menuPrice << endl;
        delete menu; //cleaning memory
    }
    return 0;
}
  • If you look at my implementation there is no need to even use pointers or dynamic memory. I moved the vector inside of main for I do not like using global variables unless if they are const values. With that being said I just pass the vector into the function by reference and it will populate it. The vector itself stores its contents in managed dynamic memory. Now if the OP needs for this to be in the global scope, they should wrap this vector in shared_ptr. – Francis Cugler Nov 09 '18 at 15:44
  • 1
    @FrancisCugler you are right, but I will hold my answer because in your post may be too much informations for newbie – Patryk Merchelski Nov 09 '18 at 15:49
  • 1
    @PatrykMerchelski At one time I was a newbie, and in some ways with some aspects of the C++ language I still am, in other aspects I'm intermediate to advanced, almost expert. I am 100% self taught, so I know where you are coming from; however I believe that people who are new and learning it, should be taught this kind of stuff early. I wish I knew this when I first started learning C++! – Francis Cugler Nov 09 '18 at 15:54
  • @FrancisCugler nice points of view, I will treat it as advice – Patryk Merchelski Nov 09 '18 at 15:57
  • Thanks for the tip with the loop. I changed my loop to check with getline and now my program runs start to finish, but for some reason, when I try to get input from the text file, it will get the first string `Bacon and eggs` and it will get the price on the next line, but after that, getline only returns blanks, even though there are two more items left. – Gavin Nov 09 '18 at 16:03
  • @Gavin check your text file and make sure that after the last character on each line there is no space or tab. Another words make sure you just have a carriage return after the last character of that line. – Francis Cugler Nov 09 '18 at 17:03
  • 1
    @Francis Cugler I found the problem. C++ doesn't seem to like getting string input after getting integer input. I used `ifstream::ignore(256,'\n');` after the price was inputted from the text file. Other than some awkward indenting, the program works fine. Thanks for the help!(thanks everybody else too) – Gavin Nov 09 '18 at 17:10