简体   繁体   中英

Trying to Input to a member of a struct inside a vector of structs causes segmentation fault

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++.

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.

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.

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;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM